
    Y.jn%                    0   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m	Z	m
Z
 ddlmZ ej                  j                  d      Zej                  j!                  edd      Zej                  j!                  edd	      Zej                  j!                  edd
      Zej                  j!                  eddd      Z e	 e
d            ZdZddZddZddZddZddZddZdddeeeed	 	 	 	 	 	 	 	 	 	 	 	 	 d dZd!d"dZedk(  r e  e             y)#u  check_cron_fire_outcome.py — cokacdir cron schedule의 fire outcome을 executable로 판정.

TASK2750: 사람이 cron.log/schedule_history를 눈으로 해석해 "발사됐나/busy skip인가/misfire인가"
판정하던 것을 결정론 checker로 대체한다. read-only — schedule store/cron.log/schedule_history/
cron-history/workspace를 읽기만 한다(write/cron/flag/daemon 변경 0). raw key/verifier 미출력.

근거: v5 recanary에서 ANU-owned callback cron(C7B5018F)이 `should execute`였으나 chat busy로
daemon이 skip → spawn 0. 이를 FIRED/misfire와 구분해야 normal callback 성공을 오판하지 않는다.

★ TASK2750-r2 견고성(Gemini findings): timezone을 KST aware로 명시 정규화(naive→서버추정 금지),
   schedule_id는 단순 substring이 아니라 word-boundary exact match(false-positive 방지).

출력 enum (7):
  FIRED_AND_SPAWNED      — schedule_history/cron-history fire record + spawned workspace 존재
  FIRED_NO_SPAWN_PROOF   — fire record 있으나 spawned workspace 없음
  BUSY_SKIP_ALREADY_AWAKE— cron.log에 should execute + chat busy/already pending skip
  PENDING_NOT_DUE        — now < expected_fire_at (아직 due 아님)
  DUE_BUT_NOT_FIRED      — due 지났고 schedule 존재하나 fire/skip 기록 없음
  REMOVED_BEFORE_FIRE    — schedule 제거됨 + fire proof 0
  MISFIRE_UNKNOWN        — 위 어디에도 안 맞음(추가 조사 필요)
    )annotationsN)datetimetimezone	timedelta)Optional~z	.cokacdirscheduleschedule_history	workspacedebugzcron.log	   )hours)FIRED_AND_SPAWNEDFIRED_NO_SPAWN_PROOFBUSY_SKIP_ALREADY_AWAKEPENDING_NOT_DUEDUE_BUT_NOT_FIREDREMOVED_BEFORE_FIREMISFIRE_UNKNOWNc                   | sy| j                         }d|vrd|v r|j                  ddd      n|}|j                  d      r|dd dz   n|}	 t        j                  |      }|j
                  |j                         S |j                  t              j                         S # t        $ r Y nw xY wd	D ]P  }	 t        j                  |dd
 |      }|j                  t              j                         c S # t        $ r Y Nw xY w y)u   schedule 문자열 → epoch(UTC). tz 명시(+09:00 등)면 그대로, naive면 **KST aware**로 정규화.

    서버 로컬 tz 추정(naive datetime의 .timestamp())을 쓰지 않는다 — 다른 tz 환경에서 오판 방지.
    NT    Zz+00:00)tzinfo)z%Y-%m-%d %H:%M:%Sz%Y-%m-%dT%H:%M:%S   )
stripreplaceendswithr   fromisoformatr   	timestampKST
ValueErrorstrptime)srawisoiso_zdtfmts         M/home/jay/workspace/tests/regression/../../scripts/check_cron_fire_outcome.py_parse_fire_atr-   5   s   
 
'')C'*#~#*#++c3
"3C#&<<#4CHx#E##E*99 <<>!zzz%//11  : 	""3s8S1B::S:)3355 			 s*   0B" >#B" "	B.-B.7<C66	DDc                d    t        j                  dt        j                  |      z   dz   |       duS )u1  cron.log 라인에 schedule_id가 **정확히** 등장하는지(앞뒤 영숫자 경계). 단순 substring 금지.

    cokacdir cron.log 패턴: 'id=C7B5018F,', '/C7B5018F.json', 'id=C7B5018F ' 등.
    sid 앞뒤가 영숫자가 아니어야 매칭(다른 id의 부분문자열 false-match 방지).
    z(?<![0-9A-Za-z])z(?![0-9A-Za-z])N)researchescape)linesids     r,   	_id_matchr4   Q   s0     99(299S>9<NNPTU]aaa    c                   t         j                  j                  | | d      }t         j                  j                  |      sy 	 t	        |dd      5 }t        j                  |      cd d d        S # 1 sw Y   y xY w# t        $ r Y y w xY w)Nz.jsonrutf-8)encoding)ospathjoinexistsopenjsonload	Exception)storer3   pfs       r,   _read_schedule_filerE   Z   ss    
Use5M*A77>>!!S7+ 	 q99Q<	  	  	  s0   A> A2(	A> 2A;7A> ;A> >	B
	B
c                   ddddd}| gt        t        j                  | dz               z   }|D ]  }t        j                  j	                  |      s#	 t        |ddd      5 }|D ]P  }t        ||      sd|d	<   |j                         }d
|v rd|d<   d|v sd|v r	d|v rd|d<   d|v s	d|v sd|v sLd|d<   R 	 ddd        |S # 1 sw Y   xY w# t        $ r Y w xY w)u[   cron.log에서 sid 관련 신호 추출(read-only, exact id match, 키/verifier 미수집).F)appearsshould_executechat_busy_skip	executingz.[0-9]r7   r8   r   r9   errorsTrG   zshould executerH   z	chat busyzalready pendingskiprI   rJ   spawnrunningN)	sortedglobr:   r;   r=   r>   r4   lowerrA   )cron_logr3   sigpathsr;   rD   r2   lows           r,   _cron_log_signalsrW   e   s   uCJ		(X*= >??E ww~~d#	dC')D 0 
0D$T3/ %)C	N**,C'3.04,-#s*0AS0HVWZ]04,-"c)W^yC?O+/K(
00	$ J0 0  		s1   C"AC1C9CC	
C	CCc                   t         j                  j                  | | d      }t         j                  j                  |      sy	 t	        |ddd      5 }|D ]~  }|j                         s	 t        j                  |      }|'t        |j                  d            t        |      k7  rS|j                  d      s|j                  d	      sv d d d        y
 	 d d d        y# t        $ r Y w xY w# 1 sw Y   yxY w# t        $ r Y yw xY w)Nz.logFr7   r8   r   rK   chat_idtsstatusT)r:   r;   r<   r=   r>   r   r?   loadsrA   strget)history_dirr3   rY   rC   rD   r2   ds          r,   _history_fire_recordra      s    
[SE,/A77>>!!S79= 	  
 zz|

4(A &3quuY/?+@CL+P55;!%%/	  	 
 	   ! 	    sg   C= C1,C"AC1C1C= C1C= "	C.+C1-C..C11C:6C= :C= =	D	D	c                |    t         j                  j                  t         j                  j                  | |            S N)r:   r;   isdirr<   )workspace_rootr3   s     r,   _spawned_workspacerf      s$    77==nc:;;r5   )rY   expected_fire_atnowrB   r_   re   rS   c                    ||n&t        j                  t              j                         }t	        ||       }t        ||       }	t        || |      }
t        ||       }|rt        |      n&|r#t        t        |j                  d                  nd }|
s|r|rdnd}n(|	d   r|	d   rd}n|||k  rd}n|d}n|||k\  rd	}nd
}| ||d u|r|j                  d      nd |r|j                  d      nd |||d uxr ||k\  |	d   |	d   |	d   |
|ddS )Nr	   r   r   rH   rI   r   r   r   r   r   rY   rG   )schedule_file_presentschedule_chat_idschedule_strfire_at_epoch	now_epochduecron_log_appearscron_log_should_executecron_log_chat_busy_skiphistory_fire_recordspawned_workspace)schedule_idoutcomeevidence)r   rh   r#   r"   rE   rW   ra   rf   r-   r]   r^   )ru   rY   rg   rh   rB   r_   re   rS   schedrT   fired_recordspawnedfire_atrv   s                 r,   classifyr|      sK    #hll3&7&A&A&CC{3E
Hk
2C'['JL =G2Bn-.6;s599Z012  w)0%6L		3'7#8+		w#	'		%# #%*$%68=		) 445:EIIj1$4':C7N #I'*+;'<'*+;'<#/!(
 r5   c           
        t        j                  d      }|j                  dd       |j                  dt        d        |j                  dd d	
       |j                  dt        d        |j                  dt
               |j                  dt               |j                  dt               |j                  dt               |j                  |       }t        |j                  |j                  |j                  |j                  |j                  |j                   |j"                  |j$                        }t'        t)        j*                  |dd             y)Ncheck_cron_fire_outcome)progz--schedule-idT)requiredz	--chat-id)typedefaultz--expected-fire-atuP   'YYYY-MM-DD HH:MM:SS'(KST naive) 또는 ISO offset. 미지정 시 schedule file.)r   helpz--now-epochz--store)r   z--history-dirz--workspace-rootz
--cron-log)ru   rY   rg   rh   rB   r_   re   rS   F   )ensure_asciiindentr   )argparseArgumentParseradd_argumentintfloatDEFAULT_STOREDEFAULT_HISTORYDEFAULT_WORKSPACEDEFAULT_CRON_LOG
parse_argsr|   ru   rY   rg   rn   rB   r_   re   rS   printr?   dumps)argvaparess       r,   mainr      s   		 	 &?	@BOOOdO3OOKc4O8OO($k  mOOMtO<OOI}O5OOO_O=OO&0AOBOOL*:O;
dA
q}}aii$%$6$6AKKamm"#"2"2QZZIC 
$**SuQ
78r5   __main__)r&   r]   returnOptional[float])r2   r]   r3   r]   r   bool)rB   r]   r3   r]   r   zOptional[dict])rS   r]   r3   r]   r   dict)r_   r]   r3   r]   rY   Optional[int]r   r   )re   r]   r3   r]   r   r   )ru   r]   rY   r   rg   zOptional[str]rh   r   rB   r]   r_   r]   re   r]   rS   r]   r   r   rc   )r   r   )!__doc__
__future__r   r   rQ   r?   r:   r/   r   r   r   typingr   r;   
expanduserHOMEr<   r   r   r   r   r#   OUTCOMESr-   r4   rE   rW   ra   rf   r|   r   __name__
SystemExit r5   r,   <module>r      sJ  * #    	 	 2 2 	ww#T;
;'',,t[2DEGGLL{K@ 77<<k7JG  yq!"8b4,< <@/3D'O#4-	-,-:I--69- !- 	- 37	-`( z
TV
 r5   