
     j[                    r   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 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mZmZmZ dZdZd%d	Zd
dd&dZdddd	 	 	 	 	 d'dZd
d
dd
d	 	 	 	 	 d(dZ G d d      Z G d d      Z  G d d      Z! G d d      Z" G d d      Z# G d d       Z$ G d! d"      Z% G d# d$      Z&y))u  tests/regression/test_worktree_timer_reconcile_2528.py — 회귀 7건.

회장 명시 task-2528 P0:
  worktree에서 완료된 task가 task-timers.json에 reconcile 안 되는 lifecycle bug fix.
  본 회귀는 task-2527 audit (2026-05-10)에서 식별된 root cause를 박제한다.

회귀 7건:
  1. happy path: worktree 완료 → timer entry 추가 PASS
  2. task-timers.json 부재 fixture (5/9 14:32 8 task 사고): 8개 task 부재 → reconcile 후 8개 모두 entry 존재
  3. idempotent: reconcile 2회 실행 → entry 1개만 존재 (중복 X)
  4. archived 충돌: archived에 이미 있는 task → active에 중복 추가 X
  5. mtime fallback 회귀: reconcile 후 helpers.py:450-454 mtime fallback 미발동 검증
  6. chat=6937032012 격리: 다른 chat record fixture → reconcile 결과에 포함 X
  7. token raw 0: reconcile 결과 JSON에 ghs_/ghp_/github_pat_ prefix 부재
    )annotationsN)Path)Any)LifecycleEvidence_build_reconciled_timer_entry_read_timers_file(_reconcile_worktree_completion_to_timers_timer_entry_present	reconcile	task-2514	task-2515z	task-2516ztask-2516+1	task-2517ztask-2518-self-host	task-2519ztask-2519-self)ghs_ghp_github_pat_c                     t        di dddd dd dd dddd d	d d
d dd dddddddddddddd dddd dddd}|j                  |        t        di |S )Ntask_id	task-9999	pr_numberpr_statemerge_commitmerged_into_mainF	ci_statussmoke_statustimer_statustimer_end_timehas_donehas_done_ackedhas_merge_donehas_qc_resulthas_followuphas_escalate_markerescalate_marker_age_minutestelegram_reply_truncatedbot_session_statusworktree_existsbranch_pushed_to_remote )dictupdater   )	overridesbases     J/home/jay/workspace/tests/regression/test_worktree_timer_reconcile_2528.py_make_evidencer0   :   s       	
           "  %)!" "'#$  %& '( !&)D, 	KK	$t$$    T)with_activec                   | dz  dz  j                  dd       |r0| dz  dz  j                  t        j                  di id      d	
       | S )z;Create memory/ tree under tmp_path. Returns workspace root.memoryeventsTparentsexist_oktask-timers.jsontasksFensure_asciiutf-8encoding)mkdir
write_textjsondumps)tmp_pathr2   s     r/   _setup_workspacerE   U   s\    8#**4$*G	H	1	1==JJ}59 	> 	
 Or1   	dev3-teamg     J@PASS)team_idduration_seconds	qc_resultc                   | dz  dz  | dz  }|j                  t        j                  |||||d|dd      d	       y )
Nr4   r5   z.donedone)r   rH   end_timerI   rJ   statuscompleted_atFr;   r=   r>   )rA   rB   rC   )	workspacer   rM   rH   rI   rJ   ps          r/   _write_done_jsonrR   `   s^    Hx'WIU*;;ALL

""$$4&  ( 	
   r1   MERGED)r   r!   r   r   c               .    t        | d|d|dd|d|d      S )NF   (aaaa1111bbbb2222cccc3333dddd4444eeee5555SUCCESSrG   T)r   r   r   r   r   r   r   r   r    r!   r"   )r0   )r   r   r!   r   r   s        r/   _evidence_completedrX   t   s0    ?)% r1   c                       e Zd ZdZddZddZy)TestHappyPathu7   회귀 #1: worktree 완료 → timer entry 추가 PASS.c           	        t        |      }d}d}t        |||dd       t        |      }g }g }t        |d||d||      }|d	   du s
J d
|        d|v sJ |g k(  sJ t	        |dz  dz        }	||	d   v s
J d|	        |	d   |   }
|
d   |k(  sJ |
d   |k(  sJ |
d   dk(  sJ |
d   dk(  sJ |
d   t        j                  d      k(  sJ |
d   dk(  sJ |
d   dk(  sJ |
d   dk(  sJ y )N	task-29992026-05-09T03:02:43+00:00rF   gffffft@)rM   rH   rI   run-1Tworkspace_rootapplyactions_takenactions_plannedentry_insertedzmeta=reconciled_worktree_timerr4   r9   r:   ztimers=r   rM   rN   	completedrH   rI   reconciled_from	done_jsonended_by lifecycle_reconciliation_managerreconcile_run_id)rE   rR   rX   r	   r   pytestapprox)selfrD   wstidrM   evrb   rc   metatimersentrys              r/   test_happy_path_inserts_entryz+TestHappyPath.test_happy_path_inserts_entry   s{   h'.S8[[ab %#%%'7'+
 $%-=tf~=-*m;;;"$$$"2=3E#EFfWo%9'99%w$Y3&&&Z H,,,X+---Y;...'(FMM&,AAAA&';666Z $FFFF'(G333r1   c           	         t        |      }d}t        ||d       t        |      }g }g }t        |d||d||      }|d   du sJ d|v sJ |g k(  sJ t	        |d	z  d
z        }||j                  di       vsJ y )Nr\   r]   rM   zrun-dryFr_   rd   re   r4   r9   r:   rE   rR   rX   r	   r   get)	rn   rD   ro   rp   rq   rb   rc   rr   rs   s	            r/   test_dry_run_does_not_insertz*TestHappyPath.test_dry_run_does_not_insert   s    h'S+FG %#%%'7'+
 $%...*o===""""2=3E#EF&**Wb1111r1   NrD   r   )__name__
__module____qualname____doc__ru   rz   r*   r1   r/   rZ   rZ      s    A#4J2r1   rZ   c                      e Zd ZdZddZy)TestEightStuckTaskFixtureuk   회귀 #2: 5/9 14:32 사고 fixture — 8개 task 모두 부재 → reconcile 후 8개 모두 entry 존재.c           
        t        |      }|dz  dz  j                  t        j                  di id      d       dd	d
dddddd}|j	                         D ]  \  }}t        |||        t        D ]&  }t        |      }g }g }t        |d| ||d||       ( t        |dz  dz        }	t        |	j                  di       j                               }
t        t              |
z
  }|r
J d|        t        |
      dk(  sJ t        D ]5  }|	d   |   }|d   ||   k(  sJ | d|d    d||    d       |d   dk(  r5J  y )Nr4   task-timers-archived.jsonr:   Fr;   r=   r>   r]   2026-05-09T04:18:03+00:00z2026-05-09T05:30:00+00:00z2026-05-09T06:00:00+00:002026-05-09T09:11:17+00:00z2026-05-09T11:00:00+00:002026-05-09T12:00:00+00:00z2026-05-09T13:00:00+00:00r   rw   zrun-Tr_   r9   u   미부착 task 존재:    rM   u   : end_time 손실 (got=z, expected=)rg   rh   )rE   rA   rB   rC   itemsrR   EIGHT_STUCK_TASKSrX   r	   r   setry   keyslen)rn   rD   ro   	end_timesrp   etrq   rb   rc   rs   presentmissingrt   s                r/   &test_eight_stuck_tasks_all_get_entriesz@TestEightStuckTaskFixture.test_eight_stuck_tasks_all_get_entries   s   h'	h4	4@@JJ}59 	A 	
 54464#>49	
	 !( 	3GCRr2	3 % 	C$S)B')M)+O4se!+ /		 #2=3E#EFfjj"-2245'(72?5gY??{7|q    % 	;C7OC(E$	#6 %.uZ/@.AYWZ^L\\]^6 *+{:::	;r1   Nr{   )r|   r}   r~   r   r   r*   r1   r/   r   r      s
    u1;r1   r   c                      e Zd ZdZddZy)TestIdempotentuE   회귀 #3: reconcile 2회 실행 → entry 1개만 존재 (중복 X).c           
     J   t        |      }d}t        ||d       t        |      }dD ]  }g }g }t        ||||d||        t	        |dz  dz        }||d	   v sJ |d	   |   }	|	d
   dk(  sJ d       g }
g }t        |d||d|
|      }|d   du sJ |d   dk(  sJ d|
vsJ y )Nr   r   rw   )run-Azrun-BTr_   r4   r9   r:   rk   r   u3   두 번째 run이 첫 entry를 덮어쓰면 안 됨zrun-Cskippedreasontimer_entry_present_in_activere   )rE   rR   rX   r	   r   )rn   rD   ro   rp   rq   run_idrb   rc   rs   rt   actions_taken2actions_planned2meta2s                r/   test_idempotent_two_runsz'TestIdempotent.test_idempotent_two_runs  s   h'S+FG %( 	F')M)+O4!+ /	 #2=3E#EFfWo%%%w$'(G3j5jj3 %'&(8(,
 Y4'''X"AAAA*.@@@r1   Nr{   )r|   r}   r~   r   r   r*   r1   r/   r   r     s    O(Ar1   r   c                      e Zd ZdZddZy)TestArchiveCollisionuH   회귀 #4: archived에 이미 있는 task → active에 중복 추가 X.c                   t        |      }d}t        ||d       |dz  dz  j                  t        j                  d||dddd	d
iid      d       t        |      }g }g }t        |d||d||      }|d   du sJ |d   dk(  sJ |d   du sJ d|vsJ t        |dz  dz        }||j                  di       vsJ t        ||dz  dz  |dz  dz        \  }	}
|	du sJ |
dk(  sJ y )Nr   r   rw   r4   r   r:   rF   rf   gfffff@)r   rH   rM   rN   rI   Fr;   r=   r>   zrun-collisionTr_   r   r   timer_entry_present_in_archiverd   re   r9   )active_patharchive_patharchive)
rE   rR   rA   rB   rC   rX   r	   r   ry   r
   )rn   rD   ro   rp   rq   rb   rc   rr   activer   sources              r/   )test_archive_present_blocks_active_insertz>TestArchiveCollision.test_archive_present_blocks_active_insert@  sn   h'S+FG 
h4	4@@JJ'*'2(C&106
 #  	A 	
$ !%#%%'7'+
 I$&&&H~!AAAA$%...*-??? #2=3E#EF&**Wb1111 /X(::h)DD

 $"""r1   Nr{   )r|   r}   r~   r   r   r*   r1   r/   r   r   =  s
    R6#r1   r   c                       e Zd ZdZddZddZy)TestMtimeFallbackRegressionuO   회귀 #5: reconcile 후 dashboard helpers.py:450-454 mtime fallback 미발동.c           	     *   t        |      }d}d}t        |||       t        |      }t        |d||dg g        t	        |dz  dz        }|d	   |   }|j                  d
      }|sJ d       |j                  d
      }	|	 }
|
du sJ d       |	|k(  sJ y)uW   helpers.py:450-454 등가 로직: task-timers.json의 end_time 우선, 없으면 mtime.r   r]   rw   r^   Tr_   r4   r9   r:   rM   u>   reconcile 후 end_time 부재 → mtime fallback 발동 위험Fuv   task-timers.json end_time이 있는데도 mtime fallback이 발동되면 안 됨 (helpers.py:450-454 회귀 게이트)Nrx   )rn   rD   ro   rp   true_end_timerq   rs   rt   end_time_from_timerstask_info_end_timeused_mtime_fallbacks              r/   ?test_end_time_present_after_reconcile_so_mtime_fallback_skippedz[TestMtimeFallbackRegression.test_end_time_present_after_reconcile_so_mtime_fallback_skipped  s    h'3S=9 %0"Tb	
 #2=3E#EFw$$yy4#e%ee# #YYz2"44"e+ 	
4	
+ "]222r1   c           	         t        |      }t        ddd      }g }g }t        dd||d||      }|d   du sJ |d	   d
k(  sJ t        |dz  dz        }d|j	                  di       vsJ y)uB   완료 evidence 없는 in-progress task는 reconcile하지 않음.r   OPENrunning)r   r   r   zrun-xTr_   r   r   no_completion_evidencer4   r9   r:   N)rE   r0   r	   r   ry   )rn   rD   ro   rq   rb   rc   rr   rs   s           r/   !test_no_completion_evidence_skipsz=TestMtimeFallbackRegression.test_no_completion_evidence_skips  s    h'K&yY#%%'7"T'

 I$&&&H~!9999"2=3E#EF&**Wb"9999r1   Nr{   )r|   r}   r~   r   r   r   r*   r1   r/   r   r   }  s    Y3B:r1   r   c                      e Zd ZdZddZy)TestChatIsolationuQ   회귀 #6: 다른 chat record fixture는 reconcile 결과에 포함되지 않음.c                   t        |      }d}t        ||d       |dz  }|j                  dd       |dz  j                  t	        j
                  dd	d
dddd      dz   d       t        |      }t        |d||dg g       }|d   du sJ t        |dz  dz        }|d   |   }t	        j
                  |d      }	d|	vsJ d       d
|	vsJ d       d|	vsJ d|	vsJ y)u   task-2528 fix는 task-timers.json만 건드림. 다른 chat의 schedule history /
        cron record는 별도 namespace이며 reconcile 결과 entry에 포함되어선 안 됨.r   r   rw    fake_schedule_history_other_chatTr6   z	OTHER.logz2026-05-10T14:31:35+09:00l   c(	 	OTHER1234u   task-2517 무관ok
irrelevant)tschat_idschedule_idpromptrN   response
r=   r>   zrun-isor_   rd   r4   r9   r:   Fr;   
9999999999u   다른 chat id 노출u   다른 chat schedule_id 노출
6937032012N)	rE   rR   r@   rA   rB   rC   rX   r	   r   )
rn   rD   ro   rp   other_chat_dirrq   rr   rs   rt   	entry_strs
             r/   .test_other_chat_record_not_in_reconcile_outputz@TestChatIsolation.test_other_chat_record_not_in_reconcile_output  s?    h'S+FG "$FFTD9	+	%11JJ1%*,(    	2 
	
 !%7BTb

 $%---"2=3E#EFw$JJu59	9,E.EE,)+M-MM+1BBB 9,,,r1   Nr{   )r|   r}   r~   r   r   r*   r1   r/   r   r     s
    ['-r1   r   c                       e Zd ZdZddZddZy)TestTokenRawZerouH   회귀 #7: reconcile 결과 JSON에 ghs_/ghp_/github_pat_ prefix 부재.c           	     L   t        |      }d}t        ||d       t        |      }t        |d||dg g       }|d   du sJ |dz  d	z  j	                  d
      }t        j                  |d      }t        D ]   }||vsJ d| d       ||vrJ d| d        d|vsd|vsJ y y )Nr   r   rw   z	run-tokenTr_   rd   r4   r9   r=   r>   Fr;   u"   task-timers.json에 token prefix 'u   ' 노출u    reconcile meta에 token prefix 'GITHUB_TOKENzGITHUB_TOKEN=)rE   rR   rX   r	   	read_textrB   rC   TOKEN_PREFIXES)	rn   rD   ro   rp   rq   rr   timers_text	meta_textprefixs	            r/   'test_reconciled_entry_no_token_prefixesz8TestTokenRawZero.test_reconciled_entry_no_token_prefixes  s    h'S+FG %7bTb

 $%--- H}'99DDgDVJJt%8	$ 	`F,c0RSYRZZb.cc,*_.NvhV^,__*	` [0O;4VVV4V0r1   c                    t        |      }d}t        ||d       t        |      }t        |d||      }|J fd |      D ]  }t        D ]  }||vrJ d|d d  d	        ! y )
Nr   r   rw   zrun-z)r`   c              3    K   t        | t              r&| j                         D ]  } |      E d {     y t        | t        t        f      r| D ]  } |      E d {     y t        | t
              r|  y y 7 K7 wN)
isinstancer+   valueslisttuplestr)objv_walks     r/   r   zLTestTokenRawZero.test_build_reconciled_entry_no_token_in_dict.<locals>._walk  s|     #t$ (A$Qx''(C$/ (A$Qx''(C%	 &	 ( (s!   /BA>-B B !B Bu/   reconciled entry value에 token prefix 노출:    z...)rE   rR   rX   r   r   )	rn   rD   ro   rp   rq   rt   sr   r   s	           @r/   ,test_build_reconciled_entry_no_token_in_dictz=TestTokenRawZero.test_build_reconciled_entry_no_token_in_dict  s    h'S+FG %-c7BrR   	 u 	fA( fQe*YZ[\_]_Z`Yaad(eef	fr1   Nr{   )r|   r}   r~   r   r   r   r*   r1   r/   r   r     s    RW2fr1   r   c                      e Zd ZdZddZy)TestReconcileIntegrationug   reconcile() 호출 시 task-2528 helper가 정상 동작하고 task-2518 stuck 처리와 충돌 없음.c                   t        |      }d}d}t        |||       |dz  dz  }|| dz  j                  dd	       || d
z  j                  dd	       dd}d dd}dd}t        |d||||      }	t	        |dz  dz        }
||
j                  di       v sJ d|
 d|	j                          |	j                  j                  di       j                  d      du sJ y )Nr   r   rw   r4   r5   z.done.ackedz{}r=   r>   z.merge-donec                    ~ ddddidS )NrU   rS   oidrV   )numberstatemergeCommitr*   r   s    r/   fake_pr_lookupzkTestReconcileIntegration.test_reconcile_finalized_task_with_no_timer_entry_gets_one.<locals>.fake_pr_lookup1  s    ! %'QR r1   )cwdc               ~    ~~ G d d      }| d d g dk(  r |       S | d d ddgk(  r |       }d|_         |S  |       S )	Nc                      e Zd ZdZdZdZy)tTestReconcileIntegration.test_reconcile_finalized_task_with_no_timer_entry_gets_one.<locals>.fake_runner.<locals>._Rr    N)r|   r}   r~   
returncodestdoutstderrr*   r1   r/   _Rr   ;  s    
r1   r      )gitz
merge-basez--is-ancestor   r   z	ls-remoter   )r   )argsr   _kwr   rs        r/   fake_runnerzhTestReconcileIntegration.test_reconcile_finalized_task_with_no_timer_entry_gets_one.<locals>.fake_runner9  sV    S  BQxAAtBQxE;//D4Kr1   c                    ~ i S r   r*   r   s    r/   fake_timer_loaderznTestReconcileIntegration.test_reconcile_finalized_task_with_no_timer_entry_gets_one.<locals>.fake_timer_loaderG  s
    Ir1   T)ra   r`   runner	pr_lookuptimer_loaderr9   r:   u7   reconcile() integrated path에서 entry 누락: timers=z, actions_taken=worktree_timer_reconcilerd   )r   r   returnr+   )rE   rR   rA   r   r   ry   rb   backfill_metadata)rn   rD   ro   rp   rM   r5   r   r   r   reportrs   s              r/   :test_reconcile_finalized_task_with_no_timer_entry_gets_onezSTestReconcileIntegration.test_reconcile_finalized_task_with_no_timer_entry_gets_one%  s'   h'.S84 h)	SE%	%11$1I	SE%	%11$1I	 &* 		 $*
 #2=3E#EFfjj"-- 	
EfX N#1124	
- ''++,FKOOP`aeiiiir1   Nr{   )r|   r}   r~   r   r  r*   r1   r/   r   r   "  s    q5jr1   r   )r   r   )rD   r   r2   boolr   r   )rP   r   r   r   rM   r   rH   r   rI   floatrJ   r   r   None)r   r   r   r  r!   r  r   z
str | Noner   r  r   r   )'r   
__future__r   rB   syspathlibr   typingr   rl   __file__resolveparent_WORKTREE_ROOTr   pathremoveinsert&utils.lifecycle_reconciliation_managerr   r   r   r	   r
   r   r   r   r0   rE   rR   rX   rZ   r   r   r   r   r   r   r   r*   r1   r/   <module>r     s[   #  
   h'')0077>>~#(("HHOOC'( 3~& ' 	  1%6 =A  Va/5',BESW( ;?W[/7RV",KO[l*@2 @2N4; 4;v+A +Ad9# 9#@4: 4:v*- *-b2f 2fr8j 8jr1   