
    " j2W                       d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	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 ddlmZmZmZmZmZmZ dZd	Zd
Z dZ!dZ"dZ#d!dZ$e fd"dZ%ejL                  d#d       Z'ejL                  d#d       Z(	 	 	 	 	 	 d$dZ)	 	 	 	 	 	 d$dZ*ejV                  jY                  dg d      	 	 	 	 	 	 	 	 d%d       Z-	 	 	 	 	 	 d$dZ.	 	 	 	 	 	 d$dZ/	 	 	 	 	 	 	 	 d&dZ0d'dZ1	 	 	 	 	 	 d$dZ2	 	 	 	 	 	 d$dZ3d(dZ4d(dZ5d(dZ6d(d Z7y))u  tests/regression/test_cron_timers_upsert_2533.py

회귀 테스트 — task-2533 cron --session timers upsert hook (신호등 sync fix A).

회장 §본질 (2026-05-10 신호등 sync fix A):
  cron ``--session`` 발사 직후 ``memory/task-timers.json`` 에 task entry 가 자동 갱신되지
  않아 대시보드 신호등 100% gap. 본 hook 은 cron 발사 직후 timers entry 를 atomic+idempotent
  하게 upsert. 본 회귀는 7개 시나리오를 박제하여 동일 사고가 다시 발생하지 못하도록 강제한다.

회귀 7 (정확히 7개):
  1. cron 발사 성공 → timers entry 신규 생성 (status=running, schedule_id 명시)
  2. 동일 task 재발사 → entry 1건만 유지 (idempotent — 중복 X)
  3. opt-out prompt(read_only/analysis_only/report_only) 도 upsert 호출 시 entry 생성
     (활성 표시 필수)
  4. cron 발사 실패 (rc!=0) → timers entry 미생성 (atomic)
  5. team_id 추출 정확성 — task md / explicit / prompt fallback 우선순위
  6. schedule_id 발사에서 description 에 raw token / raw uuid / raw hex key 누수 0
  7. chat=6937032012 격리 — entry 에 chat_id 명시 저장 (다른 chat entry 와 충돌 X)

회장 §보안:
  - production timers JSON 절대 건드리지 않음 (모든 테스트가 ``tmp_path`` 사용)
  - subprocess 실행 X (runner 가 stub 으로 주입)
  - raw cron prompt 가 description 에 그대로 저장되지 않음을 정적 검사
    )annotationsN)Path)ListSequenceTuple)&parse_schedule_id_from_cokacdir_stdoutrun_cron_with_timers_upsert)DEFAULT_TIMERS_PATHDESCRIPTION_MAX_LENTIMERS_TASK_KEYextract_task_id_from_promptextract_team_id_from_task_mdsanitize_description
6937032012
9999999999	5C9995CCB0b94683120a691cfz$5eee7634-b0be-4594-b84e-311ae64e557b(ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc                $     g d fd}|fS )uZ   지정 rc/stdout 을 반환하는 stub runner 와 호출 기록 list 를 함께 만든다.c                @    j                  t        |              fS )N)appendtuple)argvcallsrcstdouts    D/home/jay/workspace/tests/regression/test_cron_timers_upsert_2533.py_runnerz_make_runner.<locals>._runnerH   s    U4[!6z    )r   zSequence[str]returnzTuple[int, str] )r   r   r   r   s   `` @r   _make_runnerr"   D   s    !#E '>r   c                :    t        j                  d| dddd      S )u*   cokacdir cron 등록 정상 응답 stdout.okz...z	0 9 * * *)statusidpromptscheduleFensure_ascii)jsondumps)schedule_ids    r   
_ok_stdoutr.   O   s)    ::#		
  r   c                h    | dz  }|j                  t        j                  i dddd      d       |S )	u<   비어있는 task-timers.json fixture (production 오염 X).task-timers.jsonr   )taskscounterF   )r*   indentutf-8encoding)
write_textr+   r,   )tmp_pathps     r   empty_timersr;   \   s?     	%%ALL

RA.U1M   Hr   c                0    | dz  }|j                          |S )u)   비어있는 tasks/ 디렉토리 fixture.r1   )mkdir)r9   ds     r   empty_tasks_dirr?   g   s     	7AGGIHr   c           	     4   |dz  }|j                  dd       d}t        dt                     \  }}t        dd	|d
t        f|t        d| ||      \  }}}|dk(  sJ t        |      dk(  sJ d       |J d       |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   t        k(  sJ |d   t        k(  sJ |d   J |d   J t        j                  | j                  d            }	d|	t           v sJ |	t           d   }
|
d   dk(  sJ |
d   t        k(  sJ y)u<   cron 발사 성공 → tasks[task-2533] entry 신규 생성.task-2533.mduH   # task-2533 — cron timers upsert hook

dev1-team 헤르메스 단독.
r5   r6   uP   task-2533 진행. 본질=cron --session 발사 hook 이 task-timers.json upsert.r   r   r   /usr/local/bin/cokacdirz--cronz--chatNr   r'   chatexplicit_teamtimers_path	tasks_dirrunner   u0   runner 는 정확히 1번 호출되어야 한다u:   성공한 dispatch 직후 entry 가 반환되어야 한다task_id	task-2533team_id	dev1-teamr%   runningr-   chat_id
start_timeend_time)r8   r"   r.   r	   
CHAIR_CHATlenSCHEDULE_IDr+   loads	read_textr   )r;   r?   md_pathr'   r   rI   r   _entry	persistedsaveds              r   1test_2533_new_cron_dispatch_creates_running_entryr]   s   s   
 .GU  
 `F Ajl;ME6.'68ZP !LB5 7N7u:?NNN?ZZZ{***{***?i''';...z)))***$$$

<1171CDI)O4444o&{3E?i''';...r   c                N   |dz  }|j                  dd       d}t        dt        d            \  }}t        d	|t        | ||
      \  }}}|dk(  r|J |d   }t        dt        d            \  }}	t        d	|t        | ||	
      \  }
}}|
dk(  r|J t        j                  | j                  d            }|t           }t        |      dk(  s!J dt        |j                                       d|v sJ |d   }|d   dk(  sJ |d   dk(  sJ |d   |k(  sJ |d   |k\  sJ y)ud   동일 task_id 두 번 발사 → tasks dict 길이 1 유지, start_time 보존, schedule_id 갱신.rA   u   # task-2533

dev1-team 단독.
r5   r6   u   task-2533 진행. retry.r   FIRST_IDrB   rC   r   r'   rE   rG   rH   rI   NrQ   	SECOND_IDrJ   u(   중복 entry 생기면 안 된다, got: rL   r%   rO   r-   last_dispatch_at)r8   r"   r.   r	   rS   r+   rV   rW   r   rT   listkeys)r;   r?   rX   r'   rY   runner1rc1entry1first_startrunner2rc2entry2r[   r1   r\   s                  r   ,test_2533_redispatch_same_task_is_idempotentrm      s~    .G;gN'F :j+ABJAw0) !NCF !8***&K :k+BCJAw0) !NCF !8***

<1171CDIo&Eu:?[FtEJJLGYFZ[[?%+E?i''';...+---#$333r   opt_out_token)zread_only: truezanalysis_only: truezreport_only: truezfinalize_policy: no_prc                @   |dz  }|j                  dd       d| }t        dt                     \  }}t        d|t        | ||	      \  }}}|dk(  sJ |J d| d       |d   dk(  sJ t        j                  | j                  d            }	d|	t           v sJ y
)uh   opt-out 토큰이 prompt 에 있어도 timers upsert 는 호출되어 활성 봇 표시가 보장된다.rA   
dev1-team
r5   r6   ztask-2533 audit only. r   rB   r`   ra   Nu   opt-out 토큰 (uM   ) 이 있어도 timers upsert 는 활성 표시를 위해 호출돼야 한다r%   rO   rL   )	r8   r"   r.   r	   rS   r+   rV   rW   r   )
r;   r?   rn   rX   r'   rY   rI   r   rZ   r[   s
             r   %test_2533_optout_prompt_still_upsertsrq      s     .G}w7%m_5F*,7IAv.) !LB5 7N7 
=/)vw ?i'''

<1171CDI)O4444r   c                    |dz  }|j                  dd       | j                  d      }t        dd      \  }}t        dd	t        | ||
      \  }}}|dk(  sJ |J | j                  d      }||k(  sJ d       y)u<   runner rc != 0 → upsert hook skip. timers 파일 변경 0.rA   rp   r5   r6   r3   zboom: dispatch failedrB   r`      task-2533 진행.ra   NuI   dispatch 실패 시 timers JSON 은 한 바이트도 바뀌면 안 된다)r8   rW   r"   r	   rS   )	r;   r?   rX   beforerY   rI   r   rZ   afters	            r   )test_2533_failed_dispatch_does_not_upsertrv     s     .G}w7##W#5F*ABIAv.)" !LB5 7N7==""G"4EU?ggg?r   c                    |dz  }|j                  dd       | j                  d      }t        j                  ddid      }t	        d	|
      \  }}t        ddt        | ||      \  }}}|d	k(  sJ |J | j                  d      |k(  sJ y)u^   rc=0 인데 stdout 에 id 가 없으면 skip (atomic — schedule_id 없는 entry 안 만듦).rA   rp   r5   r6   r%   r$   Fr)   r   rB   r`   rs   ra   N)r8   rW   r+   r,   r"   r	   rS   )	r;   r?   rX   rt   
bad_stdoutrY   rI   r   rZ   s	            r   5test_2533_dispatch_ok_but_no_schedule_id_skips_upsertry     s     .G}w7##W#5FXt,5AJ*5IAv.)" !LB5 7N7==!!7!3v===r   c           	        |dz  }|j                  dd       t        dt                     \  }}t        ddt        d	| ||
      \  }}}||d   d	k(  sJ |dz  }|j                  t        j                  di i      d       t        dt                     \  }}t        ddt        d|||
      \  }}}||d   dk(  sJ |dz  }|j                          |dz  }	|	j                  t        j                  di i      d       t        dt                     \  }}t        ddt        d|	||
      \  }}}||d   dk(  sJ y)uk   우선순위:
       1. ``--team`` (explicit) > 2. task md 의 ``devN-team`` > 3. prompt body fallback
    rA   z# task-2533

dev2-team
r5   r6   r   rB   r`   u   task-2533 진행 dev9-teamrN   rD   NrM   zt2.jsonr1   z	dev2-teamtasks2zt3.jsonu'   task-2533 진행 dev7-team 헤르메스z	dev7-team)r8   r"   r.   r	   rS   r+   r,   r=   )
r;   r?   r9   mdrY   rI   rZ   timers2empty_tasks_dir2timers3s
             r   %test_2533_team_id_extraction_priorityr   7  s    
>	)BMM.MA*,7IAv-)+! !KAq% y!1[!@@@ "Gtzz7B-07C*,7IAv-)+!KAq% y!1[!@@@  (*"Gtzz7B-07C*,7IAv-)8"KAq% y!1[!@@@!@r   c                .   | dz  }|j                  dd       t        |      dk(  sJ |j                  dd       t        |      dk(  sJ |j                  dd       t        |      d	k(  sJ t        |d
      d
k(  sJ | dz  }t        |      d	k(  sJ y)u/   ``extract_team_id_from_task_md`` 단위 동작.rA   u   dev1-team 단독
r5   r6   rN   u   dev3 헤르메스만
z	dev3-teamu   아무 단어도 없음
zunknown-teamcustom)fallbackz
no-such.mdN)r8   r   )r9   r|   missings      r   -test_2533_extract_team_id_from_task_md_helperr   m  s    	N	"B MM&M9'+{::: MM*WM='+{::: MM-M@'+~==='X>(JJJ %G'0NBBBr   c                D   |dz  }|j                  dd       dt         dt         dt         }t	        dt               	      \  }}t        d
|t        | ||      \  }}}|J | j                  d      }t        |vsJ d       t        |vsJ d       t        |vsJ d       |d   }t        |vsJ t        |vsJ t        |vsJ t        |      t        k  sJ t        |      }	d|	v st        |	      t        |      k  sJ t        |	vsJ t        |	vsJ t        |	vsJ y)u   raw token / raw uuid / raw hex key 가 prompt 에 있어도 description 에 그대로 저장 X.

    redact 마커 (``<redacted-...>``) 만 남고 raw 값은 timers JSON 어디에도 등장하면 안 된다.
    rA   rp   r5   r6   u   task-2533 진행. token=z key=z	 session=r   rB   r`   ra   Nu6   raw GHP token 이 timers JSON 에 있으면 안 된다u4   raw hex key 가 timers JSON 에 있으면 안 된다u1   raw UUID 가 timers JSON 에 있으면 안 된다descriptionz<redacted-token>)r8   RAW_GHP_TOKENRAW_HEX_KEYRAW_UUIDr"   r.   r	   rS   rW   rT   r   r   )
r;   r?   r|   r'   rY   rI   rZ   persisted_textdesc	sanitizeds
             r   7test_2533_no_raw_token_or_uuid_in_persisted_descriptionr     sl    
>	)BMM-'M2 #=/{m9XJW  *,7IAv-) !KAq% !++W+=N .h0hh.n,d.dd,>)^+^^) D$$$d"""4t9++++ %V,I*c)ns6{.JJJ	)))i'''9$$$r   c                   |dz  }|j                  dd       t        dt        d            \  }}t        dd	t        | ||
      \  }}}|J |d   t        k(  sJ t        dt        d            \  }}t        dd	t
        | ||
      \  }}}|J |d   t
        k(  sJ t        j                  | j                  d            }|t           d   }|d   t
        k(  sJ |d   dk(  sJ y)u   chat=6937032012 entry 와 다른 chat entry 가 같은 task_id 라도 chat_id 가 명시되어
    구분 가능해야 한다 (현재 schema 는 task_id 단일 key — 본 테스트는 chat_id 가 entry 내부에
    명시 저장됨을 검증).rA   rp   r5   r6   r   CHAIR_SCHEDrB   r`   u   task-2533 진행 dev1-teamra   NrP   OTHER_SCHEDrL   r-   )
r8   r"   r.   r	   rS   
OTHER_CHATr+   rV   rW   r   )	r;   r?   r|   rY   rI   entry_chairentry_otherr[   r\   s	            r   .test_2533_chat_isolation_and_chat_id_persistedr     s%    
>	)BMM-'M2 *]*CDIAv3)+ !Aq+ """y!Z/// *]*CDIAv3)+ !Aq+ """y!Z///

<1171CDIo&{3Ez)))=000r   c                 x    t        d      dk(  sJ t        d      dk(  sJ t        d      J t        d      J y)uF   task_id 추출 — 정상 / 누락 / 합성 task (task-NNNN+M) 모두.u   task-2533 진행rL   zfoo bar task-100+1 bazz
task-100+1zno task hereN )r   r!   r   r   .test_2533_extract_task_id_from_prompt_variantsr     sL    &'9:kIII&'?@LPPP&~6>>>&r*222r   c                 4   t        t        d            dk(  sJ t        d      J t        d      J t        t        j                  ddd            	 J t        t        j                  dd	i            	 J d
t        d      z   } t        |       dk(  sJ y)uV   parse_schedule_id_from_cokacdir_stdout — JSON 노이즈 / 손상 / 실패 케이스.SID1r   Nznot jsonerrorx)r%   r&   r%   r$   z[info] starting...

WITH_NOISE)r   r.   r+   r,   )noisys    r   5test_2533_parse_schedule_id_handles_noise_and_invalidr     s     2*V2DEOOO1"5===1*=EEE1

gS12	   2

Hd#$	   #Z%==E1%8LHHHr   c                 l    t         j                  dk(  sJ t         j                  j                  dk(  sJ y)u   DEFAULT_TIMERS_PATH 가 worktree memory/task-timers.json 으로 결정되어야 한다.

    production 환경 정합 — task-2528 / task-2470 entry 가 같은 파일에 있어야 신호등이
    한 단일 source 를 본다.
    r0   memoryN)r
   nameparentr!   r   r   .test_2533_default_timers_path_points_to_memoryr   	  s3     ##'9999%%**h666r   c                     t         dz  dz  } | j                         st        j                  d       | j	                  d      }d|vsJ d       d|vsJ d	       d
|vsJ d       y)u   본 PR 시점 production timers JSON 에 raw GHP/UUID 패턴 0 — task-2533 hook 이
    description sanitize 를 적용한 결과로 새 entry 를 박을 때 누수 0임을 회귀로 박제.r   r0   z)production timers not present in worktreer5   r6   ghp_u:   GHP PAT prefix 가 timers JSON 에 등장하면 안 된다ghs_u:   GHS PAT prefix 가 timers JSON 에 등장하면 안 된다ghu_u:   GHU PAT prefix 가 timers JSON 에 등장하면 안 된다N)_WORKTREE_ROOTexistspytestskiprW   )timerstexts     r   7test_2533_production_timers_json_no_raw_secrets_residuer     s{     h&);;F==??@W-D [[[[[[[[[r   )r   intr   strr    z$'Tuple[List[Sequence[str]], object]')r-   r   r    r   )r9   r   r    r   )r;   r   r?   r   r    None)r;   r   r?   r   rn   r   r    r   )r;   r   r?   r   r9   r   r    r   )r9   r   r    r   )r    r   )8__doc__
__future__r   r+   syspathlibr   typingr   r   r   r   __file__resolver   r   r   pathremoveinsertscripts.safe_cron_dispatchr   r	   utils.cron_timers_upsertr
   r   r   r   r   r   rS   r   rU   r   r   r   r"   r.   fixturer;   r?   r]   rm   markparametrizerq   rv   ry   r   r   r   r   r   r   r   r   r!   r   r   <module>r      s?  0 #  
  ( ( 
 h'')0077>>~#(("HHOOC'( 3~& '  

 1: #. 
    '/'/)-'/	'/\/4/4)-/4	/4l 555 5 
	55Fhh)-h	h0>>)->	>83A3A)-3A9=3A	3AlC4,%,%)-,%	,%f)1)1)-)1	)1d3I*7\r   