
     jE[                       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Zddl	Z	ddl
Z
ddlmZ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mZ g dZd	Zd
ZdZdZdZeeeeefZde d<   dZ!dZ" ed      Z# G d d      Z$dZ%de d<   dZ&de d<    ejN                  d       ejN                  d       ejN                  d       ejN                  d       ejN                  d      fZ(de d<    ejN                  d       Z) ejN                  d!      Z* ejN                  d"      Z+d@d#Z,dAd$Z-dBdCd%Z.dDd&Z/ ed'(       G d) d*             Z0	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dEd+Z1dd,	 	 	 	 	 dFd-Z2 ed'(       G d. d/             Z3 ed0      d1dGd2Z4e4d3	 	 	 	 	 dHd4Z5d'ejl                  dd5	 	 	 	 	 	 	 	 	 dId6Z7 ed'(       G d7 d8             Z8d9Z9de d:<   dd;	 	 	 	 	 dJd<Z:dKd=Z;dLdMd>Z<e=d?k(  r e
j|                   e<              yy)Nu
  utils/cron_targeting_audit.py — task-2526 §4 + §5.

회장 §본질 (2026-05-10):
  독립 bot task cron에 ``--session`` 옵션이 잘못 붙어 봇이 아니라 **아누 자기 세션이
  resume된 사고**를 다시 일으키지 않기 위한 audit / kill / recover helper.

본 모듈의 4가지 책임:
  1. **Audit JSONL 기록 (회장 §4)** —
     ``memory/orchestration-audit/cron-targeting-audit.jsonl``에 8 필드 + 보강 2 필드를
     append한다. token / raw key / session secret 원문은 절대 기록하지 않는다.
  2. **Sanitize helper** — ``--key`` / ``--session`` / 환경변수 ``*KEY*`` / ``*TOKEN*``
     값을 정적 패턴으로 ``<redacted>``로 치환한다.
  3. **Misroute detection (회장 §5)** — 잘못된 ``--session`` cron PID를 감지하고
     soft kill (dry-run 우선)을 제안한다.
  4. **Evidence-based recover (회장 §5)** — worktree / PR / CI / audit 7 signal로
     실제 변경 발생 여부를 점검하고 ``contaminated_execution`` 또는 ``clean_abort``로
     분류한다.

회장 §금지:
  - ❌ token / raw key / session secret 원문 기록
  - ❌ admin override / owner_pat fallback
  - ❌ dispatch.py / cokacdir CLI 본체 수정
  - ❌ Critical 7종 외 회장 보고
  - ❌ SIGKILL (soft kill = SIGTERM only, dry-run default)

CLI:
  python3 utils/cron_targeting_audit.py --append --task-kind merge_task ...
  python3 utils/cron_targeting_audit.py --detect-misroute --pid 448820
  python3 utils/cron_targeting_audit.py --recover --task-id task-2526
    )annotationsN)asdict	dataclassfield)datetimetimezone)Path)CallableMappingOptionalSequenceTuple)DEFAULT_AUDIT_JSONL_PATHREQUIRED_AUDIT_FIELDS_2526TASK_KIND_INDEPENDENTTASK_KIND_MERGETASK_KIND_BOTTASK_KIND_FOLLOWUP_ROACTOR_BOT_SESSIONACTOR_ANU_SESSIONBlockedReasonCronTargetingAuditRecordMisrouteReportRecoveryPlanhash_bot_keysanitize_command_previewensure_no_raw_secretsbuild_audit_recordappend_audit_jsonldetect_misrouted_sessionsoft_kill_misroutedevidence_based_recoverindependent_task
merge_taskbot_taskfollowup_readonlyhuman_responseTuple[str, ...]ALLOWED_TASK_KINDSbot_sessionanu_sessionz5memory/orchestration-audit/cron-targeting-audit.jsonlc                  >    e Zd ZU dZdZdZdZdZdZeeeeefZ	de
d<   y	)
r   uF   짧은 enum 모음 — 새 abstraction이 아닌 좁은 상수 묶음.,independent_task_must_not_resume_anu_session'merge_task_must_not_inherit_anu_sessionbot_key_missing_for_bot_task!target_bot_session_owner_mismatch owner_pat_fallback_path_detectedr(   ALLN)__name__
__module____qualname____doc__INDEPENDENT_TASK_WITH_SESSIONMERGE_TASK_WITH_SESSIONBOT_KEY_MISSING_FOR_BOT_TASKTARGET_SESSION_OWNER_MISMATCHOWNER_PAT_FALLBACK_DETECTEDr2   __annotations__     1/home/jay/workspace/utils/cron_targeting_audit.pyr   r   d   sB    P$R!G#A $G!"D 	&$%#C r>   r   )cron_id
target_bottarget_bot_key_hashsession_id_presentsession_id_allowed	task_kindactor_expectedactor_actual_if_knownr   )command_preview_sanitizedblocked_reasonSUPPLEMENTARY_AUDIT_FIELDS_2526zghp_[A-Za-z0-9]{20,}zghs_[A-Za-z0-9]{20,}zgithub_pat_[A-Za-z0-9_]{20,}z\bsk-[A-Za-z0-9]{20,}\bz\bxoxb-[A-Za-z0-9-]{20,}\bzTuple[re.Pattern, ...]_RAW_TOKEN_PATTERNSz!(--key)(\s+|=)(['\"].*?['\"]|\S+)z%(--session)(\s+|=)(['\"].*?['\"]|\S+)zN\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)=(['\"].*?['\"]|\S+)c                r    | syt        j                  | j                  d            j                         dd S )uS   bot_key를 sha256(첫 16 hex)으로 마스킹. None / 빈 문자열은 None 반환.Nutf-8   )hashlibsha256encode	hexdigest)bot_keys    r?   r   r      s0    >>'..12<<>sCCr>   c                0    t        |       dk\  r| dd  dS y)u*   UUID-shaped 값을 short prefix로 redact.   N   u   …(redacted)z
(redacted)len)values    r?   _redact_uuidrZ      s#    
5zQ)M**r>   c                N  	 | }dd	d		fd}t         j                  ||      }d		fd}t        j                  ||      }d	d}t        j                  ||      }t        D ]  }|j                  d|      } t        j                  d      }d	fd}|j                  ||      }|S )
u  ``cokacdir --cron`` 형태의 command 문자열을 audit-safe 형태로 마스킹.

    - ``--key <value>`` → ``--key <hash>``
    - ``--session <uuid>`` → ``--session <prefix>…(redacted)``
    - ``XXX_KEY=…`` / ``XXX_TOKEN=…`` 환경변수 → ``XXX_KEY=<redacted>``
    - 알려진 token prefix (ghp_/ghs_/sk-/xoxb-) → ``<redacted>``
    - prompt 본문은 80자로 절단 (과도한 문맥 노출 차단).
    c                P    t        |       dk\  r| d   | d   k(  r| d   dv r| dd S | S )N   r   )'"   rW   )raws    r?   _strip_quotesz/sanitize_command_preview.<locals>._strip_quotes   s8    s8q=SVs2w.3q6Z3Gq9
r>   c                r     | j                  d            }| j                  d       dt        |       dS )N   ra   z <hash:>)groupr   )mrb   rc   s     r?   _key_subz*sanitize_command_preview.<locals>._key_sub   s6    AGGAJ'''!*W\#%6$7q99r>   c           	     l    | j                  d       dt         | j                  d                   S )Nra    re   )rg   rZ   )rh   rc   s    r?   _session_subz.sanitize_command_preview.<locals>._session_sub   s/    ''!*Q|M!''!*,EFGHHr>   c                *    | j                  d       dS )Nra   z=<redacted>)rg   )rh   s    r?   _env_subz*sanitize_command_preview.<locals>._env_sub   s    ''!*[))r>   z
<redacted>z#(--cron\s+)("([^"]*)"|\'([^\']*)\')c                    | j                  d      }| j                  d      | j                  d      n| j                  d      }|| j                  d      S t        |      kD  r|d  dz   }| d| dS )Nra   re   rV   r   u   …r`   )rg   rX   )rh   prefixbodymax_prompt_charss      r?   	_cron_subz+sanitize_command_preview.<locals>._cron_sub   sz    WWQZ3qwwqz<771:t9''))*U2D4&""r>   )rb   strreturnrt   )rh   zre.Matchru   rt   )_KEY_FLAG_REsub_SESSION_FLAG_RE_KEY_ENV_RErK   recompile)
commandrr   sri   rl   rn   patcron_quote_rers   rc   s
    `       @r?   r   r      s     	A: 	1%AI\1-A*!$A # %GGL!$% JJEFM# 	)Q'AHr>   c                N   t        j                  | dt              }t        D ].  }|j	                  |      }|st        d|j                  d       t        j                  d|      rt        d      t        j                  d      }|j	                  |      rt        d      y	)
u_  payload(any JSON-serializable)에 raw secret pattern이 남아있으면 ValueError.

    회장 §4 보안 정합 — audit JSONL append 직전 호출.

    sanitize_command_preview를 거친 값은 ``<hash:…>`` / ``<redacted>`` / ``…(redacted)``
    형태이므로 통과한다. 마스킹되지 않은 raw key/session/token만 차단한다.
    F)ensure_asciidefaultz6raw secret pattern detected in audit payload (pattern=z); refusing to write JSONLz--key(?:\s+|=)(?!<)\Sz:raw --key value detected in audit payload (must be hashed)z@\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\bz=raw session UUID detected in audit payload (must be redacted)N)	jsondumpsrt   rK   search
ValueErrorpatternrz   r{   )payloadtextr~   rh   full_uuid_res        r?   r   r      s     ::gE3?D" JJtKK?*DF  
yy)40H
 	
 ::KL 4 K
 	
 !r>   T)frozenc                      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<   d	ed<   ded<    ed       Zd	ed<   ddZy)r   u:   task-2526 audit record. token/session secret raw 0 노출.Optional[str]r@   rA   rB   boolrC   rD   rt   rE   rF   rG   rH   rI   c                     t        j                  t        j                        j	                         j                  dd      S )Ntz+00:00Z)r   nowr   utc	isoformatreplacer=   r>   r?   <lambda>z!CronTargetingAuditRecord.<lambda>  s(    HLLHLL,IY[3!7 r>   )default_factorytsc                    t        |       S N)r   )selfs    r?   to_dictz CronTargetingAuditRecord.to_dict  s    d|r>   N)ru   dict)r3   r4   r5   r6   r<   r   r   r   r=   r>   r?   r   r     s[    D&&N((""!! %8 9B 9r>   r   c        
           |t         vrt        dt          d|      |	1|	t        j                  vrt        dt        j                   d|	      t	        |      }
t        | |t        |      |duxr |dk7  t        |      ||||
|	
      }|S )uR   raw bot_key / raw session_id를 안전하게 hash/flag로 변환해 record 빌드.ztask_kind must be one of z, got Nzblocked_reason must be one of z or None, got  )
r@   rA   rB   rC   rD   rE   rF   rG   rH   rI   )r)   r   r   r2   r   r   r   r   )r@   rA   rS   
session_idrD   rE   rF   rG   command_previewrI   	sanitizedrecords               r?   r   r     s     **'(:';6)O
 	
 !nM<M<M&M,]->->,? @!$&
 	

 )9I%(1%T1FjB6F 23%3"+%F Mr>   
audit_pathc                  |t        |      nt        }| j                         }t        |       |j                  }t        |      r|j                  dd       t        j                  |d      dz   }t        |dd	      5 }t        j                  |j                         t        j                         	 |j                  |       |j                          t!        j"                  |j                                t        j                  |j                         t        j$                         	 ddd       |S # t        j                  |j                         t        j$                         w xY w# 1 sw Y   |S xY w)
uL   fcntl 락으로 record를 JSONL append. raw secret 정적 검증 후 기록.NTparentsexist_okFr   
arM   encoding)r	   r   r   r   parentrt   mkdirr   r   openfcntlflockfilenoLOCK_EXwriteflushosfsyncLOCK_UN)r   r   targetr   r   linefhs          r?   r   r   B  s    ",!7T*=UFnnG'"]]F
6{TD1::gE2T9D	fcG	, 4BIIK/	4HHTNHHJHHRYY[!KK		U]]34 M KK		U]]34 Ms%   3E-5AD692E-64E**E--E7c                  J    e Zd ZU ded<   ded<   ded<   ded<   ded	<   ded
<   y)r   intpidr   has_session_flagr   session_id_redactedsuspected_misroutert   cmdline_preview_sanitizedreasonNr3   r4   r5   r<   r=   r>   r?   r   r   b  s%    	H&&""r>   r   z/proc)	proc_rootc                   |t        |       z  dz  }	 |j                         }|j                  dd      j                  dd      j                         S # t        t        t        f$ r Y y w xY w)Ncmdline        rM   r   )errors)rt   
read_bytesFileNotFoundErrorPermissionErrorOSErrorr   decodestrip)r   r   cmdfilerb   s       r?   _read_proc_cmdliner   l  so    #c("Y.G  " ;;w%,,WY,GMMOO 8 s   A A+*A+)cmdline_readerc               N    ||       }|t        | ddddd      S t        |      }t        t        j	                  |            }d}|r1t        j	                  |      }|rt        |j                  d            }d|v xr d|v xs d	|v }|xr |}|rd
nd}	t        | |||||	      S )u   PID의 cmdline을 검사해 ``--session <uuid>`` 동반 cron 흐름이면 misroute로 분류.

    cmdline_reader는 테스트용 주입 포인트.
    NFz<unreachable>proc_cmdline_unreadable)r   r   r   r   r   r   re   cokacdirz--cronsafe_cron_dispatchcron_dispatch_with_session_flag)r   r   r   rx   r   rZ   rg   )
r   r   r   r   has_sessionsession_redactedrh   is_cron_dispatch	suspectedr   s
             r?   r    r    u  s     S!G" $$&5,
 	
 )1I'..w78K&*##G,+AGGAJ7 
w		68w#6 	- G+ 
 0 0I  	* 
 $,$"+ r>   )dry_runkillerr   c               P   t        |       }|rdnd}d}|j                  r|s	  || t        j                         d}| |j                  |||j                  |j                  |j                  d}|t        |      nt        }	|	j                  j                  dd       t!        j"                  t$        j&                  	      j)                         j+                  d
d      dd|}
t-        |
       t/        j0                  |
d      dz   }t3        |	dd      5 }t5        j6                  |j9                         t4        j:                         	 |j=                  |       |j?                          t5        j6                  |j9                         t4        j@                         	 ddd       |S # t        t
        t        f$ r#}dt        |      j                   }Y d}~d}~ww xY w# t5        j6                  |j9                         t4        j@                         w xY w# 1 sw Y   |S xY w)u   misroute 의심 PID에 SIGTERM(soft) 신호. dry_run이면 호출만 시뮬레이트.

    SIGKILL 절대 사용 안 함 (회장 §5).
    r   sigtermFTzsigterm_failed:N)r   r   actionsignal_sentr   r   r   r   r   r   r   soft_kill_audit_2526)r   kindr   r   r   rM   r   )!r    r   signalSIGTERMProcessLookupErrorr   r   typer3   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   reportr   senteresultaudit_targetaudit_payloadr   r   s                r?   r!   r!     s    &c*F!YyFD  	:3'D
 $77--%99%+%E%EF (2'=4
#C[LdT:llhll+557??#N& M -(::m%84?D	lC'	2 4bBIIK/	4HHTNHHJKK		U]]34 MA #OW= 	:&tAw'7'7&89F	:< KK		U]]34 Ms;   F* 3H!G$-2H*G!>GG!$4HHH%c                  J    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   y
)r   rt   task_idclassificationr(   signals_checkedsignals_with_evidence
next_stepsr   redispatch_requiredNr   r=   r>   r?   r   r     s%    L$$**r>   r   )worktree_diffworktree_untrackedbranch_unpushed_commitsremote_branch_existsopen_pr_for_taskci_run_for_branchaudit_jsonl_evidence_RECOVERY_SIGNALSsignalsc               P   t        | t              r| j                         st        d      !t	        | dt               t               dd      S t        fdt        D              }t        fd|D              }|rt	        | d||d	d
      S t	        | d|t               dd
      S )u   7 signal mapping으로 회복 계획 산출.

    signals dict에 ``True``인 signal이 1개라도 있으면 ``contaminated_execution``,
    모두 ``False``면 ``clean_abort``, signals=None이면 ``no_evidence``로 분류.
    ztask_id must be a non-empty strno_evidence)z.1) collect 7 signals from worktree/PR/CI/auditz22) re-run evidence_based_recover with signals dictFr   r   r   r   r   r   c              3  ,   K   | ]  }|v s|  y wr   r=   .0r}   r  s     r?   	<genexpr>z)evidence_based_recover.<locals>.<genexpr>  s     A!ALAAs   	c              3  H   K   | ]  }j                  |d       s|  yw)FN)getr  s     r?   r
  z)evidence_based_recover.<locals>.<genexpr>  s     FAu0E!Fs   ""contaminated_execution)z,1) freeze branch / no force-push / no rebaseuC   2) report contaminated_execution to chairman chat (Critical 7종 X)z<3) verify whether commit/push/merge happened on bot identityuT   4) if owner_pat fallback detected → log capability_gap, do not redispatch silentlyzT5) once chairman acknowledges, redispatch via safe_cron_dispatch with proper bot keyTclean_abort)u8   1) no commit/push/PR/CI/audit evidence → safe to abortzH2) redispatch via safe_cron_dispatch with correct bot key + no --session)
isinstancert   r   r   r   tupler  )r   r  checkedwith_evidences    `  r?   r"   r"     s     gs#7==?:;;(!G"'' !&

 
	
 A0AAGFWFFM3#"/ !%
 	
 $#g
 !
 
r>   c                    t        j                  dd      } | j                  dd      }|j                  dd	      }|j	                  d
d        |j	                  dd        |j	                  dd d       |j	                  dd d       |j	                  dd       |j	                  ddt
               |j	                  ddt        t        g       |j	                  dd        |j	                  dd       |j	                  dd        |j	                  dd        |j                  d      }|j	                  dt        d       |j                  d       }|j	                  dt        d       |j	                  d!dd"#       |j                  d$      }|j	                  d%d       |j	                  d&d d'       | S )(Ncron_targeting_auditz6task-2526 cron targeting audit / kill / recover helper)progdescriptioncmdT)destrequiredappendu   audit JSONL append 1건)helpz	--cron-id)r   z--target-botz	--bot-keyu    raw key (해시되어 기록됨))r   r  z--session-idu+   raw session id (포함 여부만 기록됨)z--session-allowed
store_true)r   z--task-kind)r  choicesz--actor-expectedz--actor-actualz--command-preview)r  z--blocked-reasonz--audit-pathdetect-misroutez--pid)r   r  	soft-killz--applyu*   기본 dry-run; --apply 시 SIGTERM 실송)r   r  recoverz	--task-idz--signals-jsonz,dict JSON e.g. '{"worktree_diff":false,...}')	argparseArgumentParseradd_subparsers
add_parseradd_argumentr)   r   r   r   )parserrw   p_appendp_detectp_killp_recs         r?   _build_arg_parserr+  3  s   $$#LF 

UT

:C~~h-F~GH+t4.$7+tA  C.$L  N-lC-$@RS,t#46G"H  J*D9-=,d;.$7~~/0H'd;^^K(F
cD9
	,I  K NN9%E	{T2	'L  NMr>   c                   t               }|j                  |       }|j                  dk(  rt        |j                  |j
                  |j                  |j                  t        |j                        |j                  |j                  |j                  |j                  |j                  
      }t        ||j                   rt#        |j                         nd       }t%        t'        j(                  dt+        |      dd             y|j                  d	k(  r?t-        |j.                        }t%        t'        j(                  t1        |      d             y|j                  d
k(  rCt3        |j.                  |j4                         }t%        t'        j(                  |d             y|j                  dk(  rd }|j6                  rt'        j8                  |j6                        }t;        |j<                  |      }t%        t'        j(                  |j<                  |j>                  tA        |jB                        tA        |jD                        tA        |jF                        |jH                  dd             y|jK                          y)Nr  )
r@   rA   rS   r   rD   rE   rF   rG   r   rI   r   T)okr   Fr   r   r  r  )r   r   r  r  r]   )&r+  
parse_argsr  r   r@   rA   rS   r   r   session_allowedrE   rF   actor_actualr   rI   r   r   r	   printr   r   rt   r    r   r   r!   applysignals_jsonloadsr"   r   r   listr   r   r   r   
print_help)	argvr&  argsr   pathr   r   sig_mapplans	            r?   	_cli_mainr<  Y  s    FT"Dxx8#LLLL#D$8$89nn.."&"3"3 00..
 "04tDOO,T
 	djjCI>USTxx$$)$((3djje<=xx;$TXX4::~Fdjje45xx904jj!2!23G%dllGDdjj||"11#D$8$89%)$*D*D%Et/#'#;#;
  	  
r>   __main__)rS   r   ru   r   )rY   rt   ru   rt   )P   )r|   rt   rr   r   ru   rt   )r   objectru   None)r@   r   rA   r   rS   r   r   r   rD   r   rE   rt   rF   rt   rG   r   r   rt   rI   r   ru   r   )r   r   r   Optional[Path]ru   r	   )r   r   r   r	   ru   r   )r   r   r   zCallable[[int], Optional[str]]ru   r   )
r   r   r   r   r   zCallable[[int, int], None]r   rA  ru   r   )r   rt   r  zOptional[Mapping[str, bool]]ru   r   )ru   zargparse.ArgumentParserr   )r7  zOptional[Sequence[str]]ru   r   )?r6   
__future__r   r!  r   rO   r   r   rz   r   sysdataclassesr   r   r   r   r   pathlibr	   typingr
   r   r   r   r   __all__r   r   r   r   TASK_KIND_HUMAN_RESPONSEr)   r<   r   r   r   r   r   rJ   r{   rK   rv   rx   ry   r   rZ   r   r   r   r   r   r   r   r    killr!   r   r  r"   r+  r<  r3   exitr=   r>   r?   <module>rK     s>  < #     	 	  
 0 0 '  ? ?8 + + +  ' O  " ! ;  ,	/ O 	4   BJJ&'BJJ&'BJJ./BJJ)*BJJ,-/ +  rzz>?2::FG bjjU
D2j
F $  (%% % 	%
 % % % % )% % "% %V "&$  
	@ $   7;7m P 6H.	. 3. 	.h )+!%2	2 2 '	2
 2 
2r $  & ?  -177 *7 	7|#L2j zCHHY[ r>   