
     jY                       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	 ddl
Z
 e	e      j                         j                  j                  j                  Z ee      ej                   v r!ej                   j#                   ee             ej                   j%                  d ee             ddlmZmZmZ ddlmZmZmZmZmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z( dZ)d	Z*d
Z+e
jX                  d'd       Z-d(dZ.d)dZ/d)dZ0d)dZ1d)dZ2d)dZ3d)dZ4d)dZ5d)dZ6d)dZ7d)dZ8d)dZ9d Z:d)dZ;d Z<d Z=d Z>d Z?d Z@d ZAd  ZBd)d!ZCd" ZDd# ZEd$ ZFd% ZGd& ZHy)*u/  tests/regression/test_cron_session_safety_guard_2526.py

회귀 테스트 — task-2526 cron ``--session`` safety guard / safe_cron_dispatch wrapper.

회장 §본질 (2026-05-10):
  독립 bot task cron에 ``--session`` 옵션이 잘못 붙어 봇이 아니라 **아누 자기 세션이
  resume된 사고**가 발생함. 본 테스트는 이 사고를 회귀 fixture로 박제하여 동일 사고가
  다시 발생하지 못하도록 wrapper preflight를 강제한다.

회장 §명시 6 회귀 검증 (정확히 6개):
  1. independent_task + ``--session`` → BLOCK
  2. merge_task + ``--session`` → BLOCK
  3. followup_readonly + ``--session`` → ALLOW
  4. bot_task + bot_key + no ``--session`` → ALLOW
  5. bot_task + no bot_key → BLOCK
  6. owner_pat fallback 감지 → BLOCK

본 사건 fixture (PR #74 재위임 오류):
  - 잘못된 cron ``5C9995CCB`` — ``--session 5eee7634-b0be-4594-b84e-311ae64e557b`` 동반
    → wrapper가 BLOCK 했어야 함 (본 테스트가 박제)
  - 재발사 cron ``74325894`` — ``--session`` 빼고 발사 → 정상 (본 테스트가 ALLOW 박제)

회장 §보안:
  - raw token / raw key / raw session UUID 정적 검사 (wrapper 출력에 0 노출)
  - subprocess 실행 X (preflight only)
  - audit JSONL은 tmp_path에만 기록 (production audit-jsonl 오염 X)
    )annotationsN)asdict)Path)DispatchStatussafe_cron_dispatchformat_chairman_block_notice)ACTOR_ANU_SESSIONACTOR_BOT_SESSIONBlockedReasonCronTargetingAuditRecordREQUIRED_AUDIT_FIELDS_2526SUPPLEMENTARY_AUDIT_FIELDS_2526TASK_KIND_BOTTASK_KIND_FOLLOWUP_ROTASK_KIND_INDEPENDENTTASK_KIND_MERGEbuild_audit_recorddetect_misrouted_sessionensure_no_raw_secretsevidence_based_recoverhash_bot_keysanitize_command_previewsoft_kill_misrouted
69370320120b94683120a691cfz$5eee7634-b0be-4594-b84e-311ae64e557bc                    | dz  S )u<   production audit-jsonl 오염 방지 — tmp_path에 격리.zcron-targeting-audit.jsonl )tmp_paths    K/home/jay/workspace/tests/regression/test_cron_session_safety_guard_2526.py
audit_pathr    Q   s     222    c                    | j                         sg S | j                  d      j                         D cg c](  }|j                         st	        j
                  |      * c}S c c}w )u>   audit JSONL 파일의 모든 line을 dict 리스트로 반환.utf-8)exists	read_text
splitlinesstripjsonloads)r    lns     r   _read_audit_linesr+   W   sO    	%/%9%9'%B%M%M%O^rSUS[S[S]DJJrN^^^s   A$
A$c           	     (   t        ddt        t        t        t        |       }|j
                  t        j                  k(  sJ |j                  t        j                  k(  sJ |j                  t               k(  sJ d       |j                  J d|j                  v sJ d|j                  v sd|j                  v sJ t        |       }t        |      d	k(  sJ |d
   }|d   t        k(  sJ |d   du sJ |d   du sJ |d   t        j                  k(  sJ y)uO   본 사건 cron 5C9995CCB — independent dev3 task에 anu self-session 동반.z)dev3-team independent_task: rebuild stats2mpromptschedulechattarget_bot_key	task_kind
session_idr    u&   BLOCK시 command_argv는 비어야 함NCRON_TARGETING_GUARD_BLOCKEDanu_sessionsession   r   r3   session_id_presentTsession_id_allowedFblocked_reason)r   
CHAIR_CHATDEV3_DAGDA_KEYr   ANU_SELF_SESSION_UUIDstatusr   BLOCKEDr;   r   INDEPENDENT_TASK_WITH_SESSIONcommand_argvtuplechairman_noticer+   lenr    resultlinesrecs       r   6test_regression_1_independent_task_with_session_blocksrJ   b   s1   :%'(F ==N22222  M$O$OOOO%')S+SS)!!---)V-C-CCCCF111Y&BWBW5WWWj)Eu:??
(C{4444#$,,,#$--- M$O$OOOOr!   c           	        t        ddt        t        t        t        |       }|j
                  t        j                  k(  sJ |j                  t        j                  t        j                  fv sJ |j                  t               k(  sJ y)u5   PR merge 위임에 anu self-session 동반 → BLOCK.uO   [task-2526] merge PR #74 — gh pr merge 74 --squash GH_TOKEN=$BOT_GITHUB_TOKENz	0 9 * * 1r.   N)r   r<   r=   r   r>   r?   r   r@   r;   r   MERGE_TASK_WITH_SESSIONTARGET_SESSION_OWNER_MISMATCHrB   rC   r    rG   s     r   0test_regression_2_merge_task_with_session_blocksrO      s    `%!(F ==N22222  --33%    %')))r!   c           	        t        ddt        dt        t        |       }|j                  t
        j                  k(  sJ |j                  J |j                  sJ d       d|j                  v sJ t        |       }t        |      dk(  sJ |d   }|d	   t        k(  sJ |d
   du sJ |d   du sJ |d   t        k(  sJ |d   J y)uC   read-only status scan followup + 같은 chat anu session → ALLOW.u6   followup readonly: status scan only — no commit/push30mNr.   ,   ALLOWED 시 command_argv가 채워져야 함	--sessionr8   r   r3   r9   Tr:   actor_expectedr;   )r   r<   r   r>   r?   r   ALLOWEDr;   rB   r+   rE   r	   rF   s       r   7test_regression_3_followup_readonly_with_session_allowsrV      s    G'(F ==N22222  (((N NN&-----j)Eu:??
(C{4444#$,,,#$,,, $5555 (((r!   c           	        t        ddt        t        t        d|       }|j                  t
        j                  k(  sJ |j                  J |j                  sJ d       d|j                  vsJ d       d|j                  v sJ t        |       }|d	   }|d
   t        t              k(  sJ |d   du sJ |d   t        k(  sJ |d   J y)uZ   재발사 cron 74325894 — --session 빼고 발사 → 정상 (다그다 trigger 박제).uU   [dev3-team] task-2526 — independent bot task, GH_TOKEN=$BOT_GITHUB_TOKEN gh pr viewr-   Nr.   rR   rS   uO   bot_task에 session이 새어들어가면 본 사건 재발 — 절대 포함 Xz--keyr   target_bot_key_hashr9   FrT   r;   )r   r<   r=   r   r?   r   rU   r;   rB   r+   r   r
   rF   s       r   5test_regression_4_bot_task_with_key_no_session_allowsrY      s    f%F ==N22222  (((N NNf111 Y1 f)))))j)E
(C$%n)EEEE#$--- $5555 (((r!   c           	         t        ddt        dt        d|       }|j                  t        j
                  k(  sJ |j                  t        j                  k(  sJ |j                  t               k(  sJ y)uA   merge cron + 회장 chat key만 (다그다 key 없음) → BLOCK.zK[task-2526] merge PR #74 GH_TOKEN=$BOT_GITHUB_TOKEN gh pr merge 74 --squashr-   Nr.   )r   r<   r   r?   r   r@   r;   r   BOT_KEY_MISSING_FOR_BOT_TASKrB   rC   rN   s     r   -test_regression_5_bot_task_without_key_blocksr\      sk    \!F ==N22222  M$N$NNNN%')))r!   c           	         t        ddt        dt        d|       }|j                  t        j
                  k(  sJ |j                  t        j                  k(  sJ y)u>   task_kind=bot_task + bot_key 누락도 BLOCK (cross-coverage).z;[dev3-team] independent bot task GH_TOKEN=$BOT_GITHUB_TOKENr-   Nr.   )	r   r<   r   r?   r   r@   r;   r   r[   rN   s     r   3test_regression_5b_bot_task_without_key_also_blocksr^      sU    LF ==N22222  M$N$NNNNr!   c           	         t        ddt        t        t        d|       }|j                  t
        j                  k(  sJ |j                  t        j                  k(  sJ |j                  t               k(  sJ y)ua   gh pr merge 명령에 GH_TOKEN=$BOT_GITHUB_TOKEN 미설정 → owner_pat fallback path → BLOCK.uD   [task-2526] merge PR #74 — gh pr merge 74 --squash --delete-branchr-   Nr.   )r   r<   r=   r   r?   r   r@   r;   r   OWNER_PAT_FALLBACK_DETECTEDrB   rC   rN   s     r   +test_regression_6_owner_pat_fallback_blocksra      sk    U%!	F ==N22222  M$M$MMMM%')))r!   c           	         t        ddt        t        t        d|       }|j                  t
        j                  k(  sJ |j                  J y)uU   대조군 — bot token이 명시 주입되면 OWNER_PAT_FALLBACK_DETECTED는 끈다.uO   [task-2526] merge PR #74 — GH_TOKEN=$BOT_GITHUB_TOKEN gh pr merge 74 --squashr-   Nr.   )r   r<   r=   r   r?   r   rU   r;   rN   s     r   8test_regression_6b_owner_pat_safe_when_gh_token_injectedrc     sM    `%!F ==N22222  (((r!   c           	         t        ddt        t        t        t        |       }|j
                  t        j                  k(  sJ d       |j                  t        j                  k(  sJ y)u   본 사건: cron 5C9995CCB — dev3 다그다에게 위임하려 했는데 --session 5eee7634...
    가 붙어 있어서 PID 448820 = 아누 자기 세션이 resume되었음.

    본 wrapper는 이 cron을 **실행 전에** 차단해야 함.
    u/   [dev3-team] task-2526 발사 — gh pr merge 74r-   r.   u|   본 사건 cron 5C9995CCB가 wrapper에 의해 BLOCK되지 않으면 PR #74 재위임 오류가 다시 발생할 수 있음.N)r   r<   r=   r   r>   r?   r   r@   r;   r   rA   rN   s     r   3test_pr74_misroute_incident_replay_blocks_5C9995CCBre   %  sf      @%'(F ==N222 	B2   M$O$OOOOr!   c           	         t        ddt        t        t        d|       }|j                  t
        j                  k(  sJ d|j                  vsJ y)u   재발사 cron 74325894 — --session 제거 후 정상 위임.

    다그다 PID 455634에서 trigger되어야 한다 (anu PID 448820 X).uL   [dev3-team] task-2526 재발사 — GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 74r-   Nr.   rS   )r   r<   r=   r   r?   r   rU   rB   rN   s     r   )test_pr74_redispatch_cron_74325894_allowsrg   <  sQ      ]%F ==N22222f11111r!   c           	        t        ddt        t        t        d|        t	        |       }t        |      dk(  sJ |d   }t        D ]  }||v rJ d|         t        D ]  }||v rJ d|         t        t              d	k(  sJ y)
uH   audit JSONL line에 회장 §4 명시 8 필드 + 2 보강 모두 존재.z0[dev3-team] task-2526 GH_TOKEN=$BOT_GITHUB_TOKENr-   Nr.   r8   r   zrequired audit field missing: z#supplementary audit field missing:    )r   r<   r=   r   r+   rE   r   r   )r    rH   rI   fnames       r   'test_audit_record_has_required_8_fieldsrk   Q  s    A% j)Eu:??
(C+ F|E=eWEE|F0 K|JB5'JJ|K )*a///r!   c                 "   t        d dt         t        d dt        t        d dt         dt         d 
      } t        | t              sJ t        |       }t        D ]  }||v rJ  | j                  t        t              k(  sJ t        |d   vsJ y )Nchat=Fz#cokacdir --cron "x" --at 2m --chat  --key )
cron_id
target_botbot_keyr4   r:   r3   rT   actor_actual_if_knowncommand_previewr;   command_preview_sanitized)r   r<   r=   r   r
   
isinstancer   r   r   rX   r   )rI   drj   s      r   -test_audit_dataclass_field_set_matches_schemarw   g  s    
:,' ("=j\Q_P`aC c3444sA+ zz ""l>&BBBB#>!????r!   c           	     B   t        ddt        t        t        t        |        t        ddt        t        t
        d|        | j                  d      }t        |vsJ d       t        |vsJ d       t        j                  d	|      rJ t        j                  d
|      rJ y)uT   audit JSONL 전체 텍스트에 raw key / raw session UUID가 1건도 없어야 함.zdev3 indep taskr-   r.   z(dev3 bot task GH_TOKEN=$BOT_GITHUB_TOKENNr#   u,   raw bot_key가 audit에 노출되면 안 됨u1   raw session UUID가 audit에 노출되면 안 됨zghp_[A-Za-z0-9]{20,}zghs_[A-Za-z0-9]{20,})	r   r<   r=   r   r>   r   r%   research)r    texts     r   "test_no_raw_secrets_in_audit_jsonlr|     s      %'( 9% (D%U'UU% ,a.aa,yy0$777yy0$7777r!   c                     dt          dt         dt         } t        |       }t        |vsJ t        |vsJ d|v sJ d|v sJ y )Nz*cokacdir --cron "do thing" --at 2m --chat rn    --session z<hash:z
(redacted))r<   r=   r>   r   )rawouts     r   5test_sanitize_command_preview_redacts_key_and_sessionr     sj    W^$4 5*+	- 
 #3
'C$$$ +++s??3r!   c                     t         dd} t        j                  t              5  t	        |        d d d        y # 1 sw Y   y xY w)Nbug)session_id_rawkind)r>   pytestraises
ValueErrorr   payloads    r   .test_ensure_no_raw_secrets_blocks_session_uuidr     s5    !6FG	z	" 'g&' ' 's	   8Ac                 ~    ddi} t        j                  t              5  t        |        d d d        y # 1 sw Y   y xY w)Ncmdz>GH_TOKEN=ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa gh pr merge)r   r   r   r   r   s    r   ,test_ensure_no_raw_secrets_blocks_github_patr     s6    VWG	z	" 'g&' ' 's   3<c                     t        t              } t        t              }| |k(  r| t        |       dk(  sJ t        d       J t        d      J y )N    )r   r=   rE   )h1h2s     r   ,test_hash_bot_key_is_deterministic_and_shortr     sS    	n	%B	n	%B83r7b=88%%%###r!   c                    dt          dt         dt         t        dfd      } | j                  du sJ | j
                  du sJ t        | j                  xs dvsJ t        | j                  vsJ t        | j                  vsJ y	)
uN   cmdline에 cokacdir + --cron + --session 가 모두 있으면 misroute 의심.5/usr/local/bin/cokacdir --cron 'task' --at 2m --chat rn   r~   4 c                    | rS d S Nr   pidfake_cmdlines    r   <lambda>zNtest_detect_misrouted_session_flags_session_in_cron_dispatch.<locals>.<lambda>      3< D r!   r   cmdline_readerTr   N)r<   r=   r>   r   suspected_misroutehas_session_flagsession_id_redactedcmdline_preview_sanitizedreportr   s    @r   <test_detect_misrouted_session_flags_session_in_cron_dispatchr     s    $W^,< =*+	- 
 &@F $$,,,""d*** )C)C)IrJJJ (H(HHHH!A!AAAAr!   c                     dt          dt         t        dfd      } | j                  du sJ | j                  du sJ y )Nr   rn   i c                    | rS d S r   r   r   s    r   r   zKtest_detect_misrouted_session_no_misroute_for_normal_cron.<locals>.<lambda>  r   r!   r   F)r<   r=   r   r   r   r   s    @r   9test_detect_misrouted_session_no_misroute_for_normal_cronr     sZ    $W^,<	>  &@F $$---""e+++r!   c                 b    t        dd       } | j                  du sJ | j                  dk(  sJ y )Nic                    | rd S d S r   r   )r   s    r   r   zGtest_detect_misrouted_session_handles_unreadable_proc.<locals>.<lambda>  s    WZt `d r!   r   Fproc_cmdline_unreadable)r   r   reason)r   s    r   5test_detect_misrouted_session_handles_unreadable_procr     s6    %(CdeF$$---==5555r!   c                h    g d	fd}t        dd||       }|d   dk(  sJ |d   du sJ g k(  sJ y )
Nc                ,    j                  | |f       y r   )append)r   siginvocationss     r   fake_killerzBtest_soft_kill_dry_run_does_not_invoke_killer.<locals>.fake_killer  s    C:&r!   r   T)dry_runkillerr    actionr   signal_sentF)r   intr   r   returnNone)r   )r    r   rG   r   s      @r   -test_soft_kill_dry_run_does_not_invoke_killerr     sT    K' !kV`aF(y(((- E)))"r!   c                     t        ddddddddd      } | j                  dk(  sJ | j                  du sJ | j                  t	               k(  sJ y )N	task-2526F)worktree_diffworktree_untrackedbranch_unpushed_commitsremote_branch_existsopen_pr_for_taskci_run_for_branchaudit_jsonl_evidencesignalsclean_abortT)r   classificationredispatch_requiredsignals_with_evidencerC   plans    r   8test_evidence_based_recover_clean_abort_when_no_evidencer     si    !""'',$) %!&$)
D -///##t+++%%000r!   c                     t        dddd      } | j                  dk(  sJ | j                  du sJ d| j                  v sJ y )Nr   T)r   r   r   contaminated_executionr   )r   r   r   r   r   s    r   =test_evidence_based_recover_contaminated_when_any_signal_truer   	  sV    !"&ED "::::##t+++d88888r!   c                 @    t        dd       } | j                  dk(  sJ y )Nr   r   no_evidence)r   r   r   s    r   9test_evidence_based_recover_no_evidence_when_signals_noner     s"    !+t<D-///r!   c                     t        t        t        j                  dt         d      } d| v sJ t        |       dk  sJ d       d| vsJ d| vsJ y )	Nrm   u   /key=<hash:abcd…>)r3   r;   rp   r5      u4   회장 짧은 보고 정합 — 200자 미만 유지CriticalCRITICAL)r   r   r   rA   r<   rE   )notices    r   7test_chairman_notice_is_short_and_not_critical_taxonomyr     si    )'$BB:,&9:F
 *V333v;TTTV###V###r!   c                     t         dz  dz  t         dz  dz  dz  t         dz  dz  dz  t         d	z  d
z  h} | D ]  }|j                         rJ d|         t        |       dk(  sJ y)u.   task-2526 expected_files = 정확히 4 파일.scriptszsafe_cron_dispatch.pytests
regressionz&test_cron_session_safety_guard_2526.pymemoryspecszcron-targeting-spec.mdutilszcron_targeting_audit.pyzexpected_file missing:    N)_WORKTREE_ROOTr$   rE   )expectedps     r   &test_expected_files_exactly_four_existr   -  s     	"%<< </2ZZ!G+.FF #<<	H  9xxz84QC88z9x=Ar!   )r   r   r   r   )r    r   r   list)r    r   )I__doc__
__future__r   r(   ry   sysdataclassesr   pathlibr   r   __file__resolveparentr   strpathremoveinsertscripts.safe_cron_dispatchr   r   r   utils.cron_targeting_auditr	   r
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r<   r=   r>   fixturer    r+   rJ   rO   rV   rY   r\   r^   ra   rc   re   rg   rk   rw   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r!   r   <module>r      sa  6 #  	 
   
 h'')0077>>~#(("HHOOC'( 3~& ' 
    2 
#>  3 3
_PB*2)@)@*"O&*$)&P.2*0,@48<
''$B&
,6
1$90
$"
r!   