
    li              	      f   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	m
Z
mZ ddlmZmZ ddlmZ ddlmZ ddlmZmZmZ  ee      j/                         j0                  j0                  Z ee      ej6                  vr"ej6                  j9                  d ee             dd	lmZmZ dd
l m!Z!m"Z"m#Z#m$Z$  ejJ                  e&      Z'edejP                  f   Z) ed      Z* ed      Z+dZ,dZ-dZ. G d dee      Z/ G d dee      Z0e
 G d d             Z1e
 G d d             Z2e
 G d d             Z3dd	 	 	 	 	 dJdZ4ddd	 	 	 	 	 	 	 dKdZ5dLdZ6dMd Z7dNd!Z8dOd"Z9dd#dPd$Z:dddd%	 	 	 	 	 	 	 	 	 dQd&Z;ddd'	 	 	 	 	 	 	 dRd(Z<ddd'	 	 	 	 	 	 	 dSd)Z=ddd*	 	 	 	 	 	 	 dTd+Z>dd,	 	 	 	 	 dUd-Z?dVd.Z@dd/	 	 	 	 	 dWd0ZAddd'	 	 	 	 	 	 	 dXd1ZBdddddd2	 	 	 	 	 	 	 	 	 	 	 	 	 dYd3ZCdZd4ZDd[d5ZEdd,	 	 	 	 	 	 	 d\d6ZF	 	 	 	 	 	 	 	 d]d7ZGd^d8ZHd_d9ZIdd:ddd;	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d`d<ZJd:dddddddd=	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dad>ZKddd:dd?	 	 	 	 	 	 	 	 	 dbd@ZLdcdAZMdddBZNddC	 	 	 	 	 dedDZOdfdEZPdgdhdFZQe&dGk(  r& ej                  ej                  dHI        eQ        yy)iuu  utils/lifecycle_reconciliation_manager.py — task-2518 P0.

회장 명시 요구사항:
  - bot session ↔ task lifecycle 결합 제거
  - GitHub/CI/smoke evidence를 source-of-truth로 사용하는 idempotent reconcile state machine
  - manual .done 위장 차단 (evidence 없으면 RuntimeError)
  - dry-run 기본 (--apply 없이 side effect 0)

7 Lifecycle States:
  RUNNING                 — task-timer running, 작업 진행 중
  PR_OPEN                 — PR 생성됨, 머지 대기
  MERGED_PENDING_RECONCILE — PR merged, finalize 누락 (회장 §1 사례)
  RECONCILING             — reconcile 진행 중
  FINALIZED               — .done.acked + .merge-done + timer end 모두 정상
  STUCK_NEEDS_RECONCILE   — stuck 자동 감지
  ESCALATED               — Critical 7종 발동

8 Stuck Cases (회장 §1~7 + Telegram cut-off):
  TIMER_RUNNING_BUT_PR_MERGED          — 회장 §2
  PR_MERGED_BUT_DONE_MISSING           — 회장 §1
  MERGE_COMMIT_BUT_MERGE_DONE_MISSING  — 회장 §1
  CI_PASS_BUT_NOT_FINALIZED
  TELEGRAM_REPLY_CUT_OFF               — cron history truncation
  BOT_SESSION_ENDED_BUT_TASK_OK        — 회장 §7
  FINISH_TASK_INTERRUPTED              — 회장 §3
  STALE_ESCALATE_MARKER                — 회장 §4

Evidence priority (회장 명시):
  PR state > mergeCommit > origin/main 포함 > CI > smoke > timer > file marker
    )annotationsN)	dataclassfield)datetimetimezone)Enum)Path)AnyCallableOptional)CanonicalWorkspaceresolve_canonical_workspace)AutomationDecisionCriticalEscalationTypeEscalationPacketSmokeResult.z/home/jay/workspacez$/home/jay/.cokacdir/schedule_historyzmemory/eventszmemory/task-timers.jsoni  c                  ,    e Zd ZdZdZdZdZdZdZdZ	dZ
y	)
LifecycleStateu1   7 lifecycle states (회장 명시 정확 매칭).RUNNINGPR_OPENMERGED_PENDING_RECONCILERECONCILING	FINALIZEDSTUCK_NEEDS_RECONCILE	ESCALATEDN)__name__
__module____qualname____doc__r   r   r   r   r   r   r        W/home/jay/workspace/.worktrees/task-2520-dev4/utils/lifecycle_reconciliation_manager.pyr   r   Q   s*    ;GG9KI3Ir!   r   c                  0    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zy
)StuckReasonu0   8 stuck cases (회장 §1~7 + Telegram cut-off).TIMER_RUNNING_BUT_PR_MERGEDPR_MERGED_BUT_DONE_MISSING#MERGE_COMMIT_BUT_MERGE_DONE_MISSINGCI_PASS_BUT_NOT_FINALIZEDTELEGRAM_REPLY_CUT_OFFBOT_SESSION_ENDED_BUT_TASK_OKFINISH_TASK_INTERRUPTEDSTALE_ESCALATE_MARKERN)r   r   r   r   r%   r&   r'   r(   r)   r*   r+   r,   r    r!   r"   r$   r$   ]   s5    :"?!=*O' ;5$C!73r!   r$   c                      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<   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<   y)LifecycleEvidenceu9   모든 source-of-truth 증거를 모은 unified envelope.strtask_idzOptional[int]	pr_numberOptional[str]pr_statemerge_commitboolmerged_into_main	ci_statussmoke_statustimer_statustimer_end_timehas_donehas_done_ackedhas_merge_donehas_qc_resulthas_followuphas_escalate_markerzOptional[float]escalate_marker_age_minutestelegram_reply_truncatedbot_session_statusworktree_existsbranch_pushed_to_remoteN)r   r   r   r   __annotations__r    r!   r"   r.   r.   n   s{    CL!!N!00""%%!!r!   r.   c                  "    e Zd ZU ded<   ded<   y)	StuckCaser$   reasonr/   detailN)r   r   r   rF   r    r!   r"   rH   rH      s    Kr!   rH   c                      e Zd ZU dZded<   ded<    ee      Zded<   d	Zd
ed<    ee      Z	ded<    ee      Z
ded<   dZded<   dZded<   dZded<    ee      Zded<   ddZddZddZy	)LifecycleReportu   reconcile 결과 envelope.r/   r0   r   state)default_factorylist[StuckCase]stuck_casesNzOptional[LifecycleEvidence]evidence	list[str]actions_takenactions_plannedTr5   dry_run 	timestampreconcile_run_idzdict[str, Any]backfill_metadatac                    | j                   i S | j                   j                  j                         D ci c]  \  }}||
 c}}S c c}}w )uI   evidence를 dict로 직렬화 (EscalationPacket 연동 등에서 사용).)rQ   __dict__items)selfkvs      r"   evidence_to_dictz LifecycleReport.evidence_to_dict   s@    == I!%!7!7!=!=!?@A1@@@s   Ac                h   | j                   | j                  j                  | j                  D cg c]%  }|j                  j                  |j
                  d' c}| j                         | j                  | j                  | j                  | j                  | j                  | j                  d
S c c}w )NrI   rJ   
r0   rM   rP   rQ   rS   rT   rU   rW   rX   rY   )r0   rM   valuerP   rI   rJ   r`   rS   rT   rU   rW   rX   rY   )r]   scs     r"   to_dictzLifecycleReport.to_dict   s    ||ZZ%% ** 99??bii@ --/!//#33|| $ 5 5!%!7!7
 	
s   *B/c                N    t        j                  | j                         dd      S )N   Findentensure_ascii)jsondumpsrf   )r]   s    r"   to_jsonzLifecycleReport.to_json   s    zz$,,.GGr!   )returndictro   r/   )r   r   r   r   rF   r   listrP   rQ   rS   rT   rU   rW   rX   rp   rY   r`   rf   rn   r    r!   r"   rL   rL      s    $L#(#>K>,0H)0$T:M9:!&t!<OY<GTIsc(-d(C~CA
"Hr!   rL   cwdc               6    t        j                  | |ddd      S )NT   )rt   capture_outputtexttimeout)
subprocessrun)argsrt   s     r"   _default_runnerr}      s$    
 >> r!   rt   runnerc               N    ||nt         } || |t        |            S d       S )Nrs   )r}   r/   )r|   rt   r   fns       r"   _runr      s-     %?BdCOC>>>>r!   c                 d    t        j                  t        j                        j	                         S N)r   nowr   utc	isoformatr    r!   r"   _now_isor      s    <<%//11r!   c                    | t         z  S r   )_EVENTS_DIR_NAMEworkspace_roots    r"   _events_dirr      s    ,,,r!   c                (    t        |       | d| z  S N.)r   )r   r0   suffixs      r"   _marker_pathr      s    ~&G9AfX)>>>r!   c                     t         S r   )_DEFAULT_WORKSPACEr    r!   r"   _default_workspace_rootr      s    r!   r   c                   	 t        | d|      S # t        $ r, t        | t        t        dz  | z  d|  ddt        dd	      cY S w xY w)zUResolve canonical workspace for the task (delegates to canonical_workspace_resolver).F)fetchr   
.worktreestask/rV   T)	r0   r   worktree_pathbranch_namemain_head_shabase_shart   is_mainis_clean)r   	Exceptionr   r   )r0   r   s     r"   _resolve_workspacer      s^    
*7%OO 
!-,|;gEy)"

 
	

s    2AAr   r   	pr_lookupc                   |		  ||       S t        |xs
 t	                     }	 t        ddddd|  dd	d
dddg||      }|j                  dk7  s|j                  j                         si S t        j                  |j                        }|si S |D ]  }|j                  d      dk(  s|c S  |d   S # t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w# t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w)z/Fetch PR info via gh CLI or injected pr_lookup.Nzpr_lookup failed for %s: %sghprrr   z--searchz
head:task/z--stateall--jsonznumber,state,mergeCommitz--limit5r~   r   rM   MERGEDzgh pr list failed for %s: %s)r   loggerwarningr/   r   r   
returncodestdoutstriprl   loadsget)	r0   r   r   r   excrt   rr\   items	            r"   _gather_pr_infor     s'    	W%%
 n9 7 9
:CdFj	2543 

 <<1AHHNN$4I

188$I 	Dxx H,	 Qx5  	NN8'3GI	6  5wD	sS   B2 AC! +"C! C! (C! ,C! 2	C;CCC!	D*DDDr   r   c                   | syt        |xs
 t                     }	 t        ddd| dg||      }|j                  dk(  S # t        $ r!}t
        j                  d| |       Y d	}~yd	}~ww xY w)
z4Check if merge_commit is an ancestor of origin/main.Fgitz
merge-basez--is-ancestorzorigin/mainr~   r   z"merge-base check failed for %s: %sN)r/   r   r   r   r   r   r   )r4   r   r   rt   r   r   s         r"   _check_merged_into_mainr   ,  su     
n9 7 9
:C	L/<O

 ||q   ;\3Os   !> 	A(A##A(c               6   t        |xs
 t                     }	 t        dddt        |       ddg||      }|j                  dk7  s|j                  j                         syt        j                  |j                        }|j                  d      xs g }|sy|D ch c]*  }|j                  d	      xs |j                  d
      xs d, }}d|v sd|v ryt        d |D              ryyc c}w # t        $ r!}	t        j                  d| |	       Y d}	~	yd}	~	ww xY w)z?Fetch CI rollup status via gh pr view --json statusCheckRollup.r   r   viewr   statusCheckRollupr~   r   NrM   
conclusionrV   FAILUREFAILEDc              3  *   K   | ]  }|s|d v   yw)>   SUCCESS	COMPLETEDNr    ).0ss     r"   	<genexpr>z$_gather_ci_status.<locals>.<genexpr>]  s     Bq,,Bs   	r   PENDINGz$CI status fetch failed for PR %s: %s)r/   r   r   r   r   r   rl   r   r   r   r   r   r   )
r1   r   r   rt   r   datarollupr   statesr   s
             r"   _gather_ci_statusr   B  s    n9 7 9
:CdFC	N- 
 <<1AHHNN$4zz!((#-.4"PVW$((7#Ctxx'=CCWW(f"4BfBB X  =y#Ns<   AC.  6C. C. /C)

C. C. )C. .	D7DDr   timer_loaderc                  |		  ||       S |xs
 t               t        z  }	 |j                         si S |j                  d      }t        j                  |      }|j                  di       }|j                  | i       S # t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w# t         $ r#}t        j                  d| |       i cY d}~S d}~ww xY w)z?Load timer info from task-timers.json or injected timer_loader.Nztimer_loader failed for %s: %sutf-8encodingtasksz'task-timers.json read failed for %s: %s)
r   r   r   r   _TIMERS_FILE_NAMEexists	read_textrl   r   r   )r0   r   r   r   timers_filerawr   r   s           r"   _gather_timer_infor   e  s     	((
 ">%<%>BSSK	!!#I##W#5zz#"%yy"%%  	NN;WcJI	  @'3O	s@   A? B. A
B. ?	B+B& B+&B+.	C7CCCr   c                   |xs
 t               }t        |      d fd} |d      }d}|rZ	   dz  j                         j                  }t	        j
                  t        j                        j                         |z
  }|dz  } |d       |d       |d       |d	       |d
      ||dS # t        $ r d}Y 5w xY w)z%Check file markers in memory/events/.c                4     d|  z  j                         S r   )r   )r   evr0   s    r"   r   z$_gather_file_markers.<locals>.exists  s"    y&**2244r!   zdone.escalatedNz.done.escalatedg      N@done
done.acked
merge-donez	qc-resultzfollowup.txt)r;   r<   r=   r>   r?   r@   rA   )r   r/   ro   r5   )
r   r   statst_mtimer   r   r   r   rW   r   )	r0   r   wdr   has_escalateescalate_agemtimeage_secsr   s	   `       @r"   _gather_file_markersr     s     
	424B	RB5 *+L$(L	 WI_55;;=FFE||HLL1;;=EH#d?L
 6N . .,~.+'3   	 L	 s   AB1 1B?>B?c                    | sy| j                         }t        | j                  d            t        k\  ry|rd|d   cxk  rdk  ry d|v r|j	                  d      }|dz  d	k7  ryy)
z<Heuristic: response is likely truncated if it ends abruptly.Fr   Tu   가u   힣z```rh   r   )rstriplenencode$_TELEGRAM_TRUNCATION_THRESHOLD_BYTEScount)rx   strippedr   s      r"   _is_truncated_responser     sq    {{}H
4;;w $HHEXb\2U2 3 u%19>r!   cron_history_dirc                  |xs t         }ddd}	 |j                         s|S d}t        |j                  d      d d      D ]  }	 |j	                  dd	
      j                         }t        |      D ]I  }|j                         }|s	 t        j                  |      }|j                  dd      xs d}	| |	v sG|} n | n ||S |j                  dd      xs d}
t        |
      |d<   |j                  dd      }|t        |      |d<   |S # t        j                  $ r Y w xY w# t        $ r Y w xY w# t        $ r"}t        j!                  d| |       Y d}~|S d}~ww xY w)zIScan cron history logs to detect Telegram cut-off and bot session status.FN)rB   rC   z*.logc                6    | j                         j                  S r   )r   r   )ps    r"   <lambda>z&_gather_cron_history.<locals>.<lambda>  s    HYHY r!   T)keyreverser   replace)r   errorspromptrV   responserB   statusrC   z#cron history scan failed for %s: %s)_DEFAULT_CRON_HISTORY_DIRr   sortedglobr   
splitlinesreversedr   rl   r   JSONDecodeErrorr   r   r   r/   r   r   )r0   r   history_dirresultmatching_last_recordlog_filelineslinerecordr   r   r   r   s                r"   _gather_cron_historyr
    s    #?&?K$)"F
(L!!#M 04{//8>Ycgh 	H **GI*NYY[$UO D::<D !!%D!1 $ZZ"5;F&(/5, (3 4!	*  'M (++J;Ar-CH-M)* &))(D9+.v;F'(
 M5  // ! !    L<gsKKMLsr   D9 #D9 AD*
DD*:D*D9 	AD9 D'$D*&D''D**	D63D9 5D66D9 9	E$EE$c          	        |xs
 t               }|dz  }ddd}	 ddl}t        ||  dz        }|j                  |      }t        |      |d<   	 t        dd	d
dd|  dgt        |      |      }	|	j                  dk(  r|	j                  j                         rd|d<   |S # t        $ r Y \w xY w# t        $ r"}
t        j                  d| |
       Y d}
~
|S d}
~
ww xY w)z9Check if worktree exists and branch was pushed to remote.r   F)rD   rE   r   Nz-*rD   r   z	ls-remotez--headsoriginr   *r~   TrE   zls-remote failed for %s: %s)r   r   r/   r5   r   r   r   r   r   r   r   )r0   r   r   r   worktrees_rootr  _globpattern
candidatesr   r   s              r"   _gather_worktree_infor    s    
	424B,&N #(Fn'"~56ZZ(
$($4 !
	DKHgYa6HIB

 <<1!104F,- M    D4gsCCMDs*   4B AB. 	B+*B+.	C7CCr   r   r   r   r   c               T   |	 t        | |      }|j                  }n|}t	        | |||      }|j                  d      }	|j                  d      }
|
rt        |
      j                         nd}|j                  d      }t        |t              r%|j                  d      xs |j                  d      }nt        |t              r|r|}nd}d	}|rt        |||
      }d}|	t        |	||
      }d}t        |      }||  dz  }|j                         r1	 |j                  d      j                         }d|v rd}n	d|v rd}nd}t        | ||      }|j                  d      }|j                  d      }t!        | |      }t#        | |      }t%        | ||
      }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)   S # t        $ r
 t        }Y w xY w# t        $ r d}Y w xY w)+u  모든 evidence source 수집 — gh/git/file/timer/cron history.

    workspace_root가 None이면 resolve_canonical_workspace(task_id, fetch=False)로
    자동 결정 (§6 CanonicalWorkspace 연동). 실패 시 fallback Path('/home/jay/workspace').
    Nr   r   numberrM   mergeCommitoidshaFr   z
.qc-resultr   r   PASSFAILSKIPPEDr   r   end_timer   r   r0   r1   r3   r4   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   r    )r   r   r   r   r   r   r/   upper
isinstancerp   r   r   r   r   r   r   r   r
  r  r.   )r0   r   r   r   r   r   cwsr   pr_infor1   	raw_stater3   raw_mcr4   r6   r7   r8   ev_dirqc_filecontent
timer_infor9   r:   markerscronwt_infos                             r"   gather_evidencer*    s     	$&8&PC##B  	G  '{{84IG$I8Ac)n224tH [['F&$&,jj&7&L6::e;L	FC	 V 2
  $I%irR	 #'L_F'*--G~~		 '''9??AG %7"%(
 $GB\ZJ",..":L$.NN:$>N #72>G  :JKD $GF2NG   "	
 *  " " & $ /0 /0 o. ^, $$9:  %,,I$J!" "&&@!A#$   45%&   12'( !((A B) K  	$#B	$h  	 L	 s#   H 0H HHH'&H'c           
         g }| j                   dk(  rT| j                  dk(  rE|j                  t        t        j
                  d| j                   d| j                   d             | j                  dk(  rD| j                  s8|j                  t        t        j                  d| j                   d             | j                  rD| j                  s8|j                  t        t        j                  d| j                   d	             | j                  d
k(  rz| j                  dk(  rk| j                  dk(  r\| j                  r| j                  sD|j                  t        t        j                  d| j                   d| j                                | j                   r8|j                  t        t        j"                  d| j$                   d             | j$                  dv rD| j&                  r8|j                  t        t        j(                  d| j$                   d             | j*                  rB| j,                  r6| j                  s*|j                  t        t        j.                  d             | j0                  r|| j2                  xs ddkD  ri| j                  dv xs | j                  dk(  xr | j&                   }|s9|j                  t        t        j4                  d| j2                  dd             |S )u   8 케이스 자동 감지.runningr   z4timer_status=running but pr_state=MERGED (pr_number=, merge_commit=)rb   z4pr_state=MERGED but .done marker missing (pr_number=merge_commit=z  present but .merge-done missingr   r  z5ci=SUCCESS, smoke=PASS, pr=MERGED but has_done_acked=z, has_merge_done=zAcron history last response appears truncated (bot_session_status=>   error	cancelledzbot_session_status=z0 but merged_into_main=True (task evidence is OK)uo   worktree exists + branch pushed to remote but no PR found — finish-task likely interrupted before PR creationr   rv   >   r   zescalate marker exists for z.1fz# min but no Critical evidence found)r9   r3   appendrH   r$   r%   r1   r4   r;   r&   r=   r'   r7   r8   r<   r(   rB   r)   rC   r6   r*   rD   rE   r+   r@   rA   r,   )rQ   caseshas_active_criticals      r"   detect_stuck_casesr5    s   E 	)h.?.?8.KY::&001AVAV@WWXZ
 	 H$X->->Y99&0014
 	 X%<%<YBB 5 566VW
 	 	i'!!V+)((0G0GY88""*"9"9!::KHLcLcKdf
 	 ((Y55''/'B'B&C1F
 	 ""&<<AZAZY<<%h&A&A%B C> ?
 	 	  ,,""Y66D
 	 ##)M)M)RQRVX(X +- N(*L83L3L/L 	 #LL"881;;C@@ce  Lr!   c                0   t        |       }| j                  dk(  rR| j                  rF| j                  dv r8| j                  r,| j
                  r | j                  dv rt        j                  g fS | j                  dk(  r|| j                  rpg }| j                  s|j                  d       | j
                  s|j                  d       | j                  dk(  r|j                  d       |rt        j                  |fS | j                  dk(  r | j                  s|rt        j                  |fS | j                  dk(  r5| j                  dk(  r&|rt        j                  |fS t        j                  g fS | j                  dk(  r5| j                  dk7  r&|rt        j                  |fS t        j                  g fS | j                  r'| j                  xs d	}|d
k  rt        j                   |fS |rt        j                  |fS | j                  | j                  t        j                  g fS | j                  dk(  r| j                  t        j                  g fS | j                  dk(  r*| j                  r| j
                  rt        j                  g fS t        j                  g fS )u   evidence priority 규칙으로 state + stuck cases 결정.

    우선순위 (회장 명시):
    PR state > mergeCommit > origin/main 포함 > CI > smoke > timer > file marker
    r   >   Nr   >   N	completedz.done.ackedz.merge-doner,  z	timer-endOPENr   rv   r7  )r5  r3   r6   r7   r<   r=   r9   r   r   r2  r   r   r   r   r@   rA   r   )rQ   rP   missingages       r"   determine_stater;    sp    %X.K 	X%%%"33####!!%88''++ H$)B)B&&NN=)&&NN=)  I-NN;'!::KGG H$X-F-F!77DD F"x'<'<	'I!77DD%%r)) F"x'<'<	'I!77DD%%r)) ##227a"9!++[88 33[@@  X%:%:%B%%r)) 	)h.?.?.G%%r)) +0G0GHLcLc''++ !!2%%r!   c                  |xs
 t               }t        || d      }|j                  dk(  xr |j                  }t	        |j
                        xr. |j                  xr  |j                  dk(  xs |j                  dk(  }|xs |}|sRt        d|  d| d|j                   d|j                   d	|j
                   d
|j                   d|j                         y)u  evidence가 부족한데 강제로 .done을 만들려고 하면 RuntimeError.

    충분한 evidence:
    - pr_state == MERGED and merged_into_main, OR
    - merge_commit not None and merged_into_main and (ci_status == SUCCESS or smoke_status == PASS)
    r   r   r   r  z"MANUAL_DONE_FORGERY_BLOCKED: task=uB    — insufficient evidence to create .done. Target path would be: zt. Requires (pr_state=MERGED + merged_into_main) OR (merge_commit + merged_into_main + ci/smoke PASS). Got: pr_state=z, merged_into_main=r-  z, ci_status=z, smoke_status=N)	r   r   r3   r6   r5   r4   r7   r8   RuntimeError)r0   rQ   r   r   	done_pathcond_acond_bhas_sufficients           r"   assert_no_manual_done_forgeryrB  C  s
    
	424BR&1I(*Hx/H/HFX""# 	Q%%	Q9,O0E0E0O 
 %vN0	 :%%.K 0 &..//B8C\C\B] ^$112,x?Q?Q>R S$1124
 	
 r!   c                   g }|j                   r|j                  d|j                           |j                  r|j                  d|j                          |j                  r|j                  d|j                          |j                  r|j                  d|j                          |j
                  r|j                  d       | d|t               ||j                  ddS )	z,Build JSON body for backfilled marker files.z	pr_state=r/  z
ci_status=zsmoke_status=zmerged_into_main=True lifecycle_reconciliation_managerz,evidence-based backfill (not manual forgery))r0   reconciled_byrX   rW   evidence_sourcer4   note)r3   r2  r4   r7   r8   r6   r   )r0   rX   rQ   rF  s       r"   _build_backfill_metadatarH  j  s     "$O8+<+<*=>?x/D/D.EFGH,>,>+?@Ax/D/D.EFG  67 ;,Z* --> r!   c                d    | j                   j                  dd       | j                  |d       y )NT)parentsexist_okr   r   )parentmkdir
write_text)pathr%  s     r"   _default_file_writerrP    s)    KKdT2OOGgO.r!   c                <   t               t        z  }	 |j                         r|j                  d      nd}t	        j
                  |      }|j                  di       }||| <   |j                  t	        j                  |dd      d       y# t        $ r i }Y Nw xY w)	z2Write updated timer data back to task-timers.json.r   r   z{}r   rh   Fri   N)
r   r   r   r   rl   r   r   
setdefaultrN  rm   )r0   
timer_datar   r   r   r   s         r"   _default_timer_writerrT    s    )+.??K9D9K9K9Mk##W#5SWzz# OOGR(EE'N4::d15IT[\	  s   9B BBF)r   applyfile_writertimer_writerc                  	 |xs
 t               }
||nt        ||nt        t         ||      }t	        |t
              r|j                  n
t        |      |d<   t        j                  |dd      d	fd}	 t         ||
       |j                  st!        |
 d      } |d|ffd	       |j"                  st!        |
 d      } |d|ffd	       |j$                  st!        |
 d      } |d|ffd	       |j&                  dk(  r t)               }d|d|d} |d|f fd	       |S # t        $ rJ}t        j                  d|       rj                  d	|        n	j                  d
       |cY d}~S d}~ww xY w)zBackfill missing markers based on evidence. Returns backfill_metadata.

    `state` is recorded in metadata so reconcile audit trails preserve which
    LifecycleState authorised the backfill.
    Nauthorising_staterh   Fri   c                    r	  |        j                  |        y j                  |        y # t        $ r!}t        j                  d| |       Y d }~y d }~ww xY w)Nzbackfill action %s failed: %s)r2  r   r   r0  )action_namer   r   rT   rS   rU  s      r"   
do_or_planz%_backfill_markers.<locals>.do_or_plan  sW    P$$[1 "";/  P<k3OOPs   0 	AAAr   zBackfill blocked: %szBLOCKED:zBLOCKED (insufficient evidence)r   created_donec                     |       S r   r    r   fw	meta_jsons    r"   r   z#_backfill_markers.<locals>.<lambda>  s    "Q	2B r!   r   created_done_ackedc                     |       S r   r    r_  s    r"   r   z#_backfill_markers.<locals>.<lambda>  s    1i8H r!   r   wrote_merge_donec                     |       S r   r    r_  s    r"   r   z#_backfill_markers.<locals>.<lambda>  s    bI6F r!   r,  r7  rD  )r   r  ended_byrX   ended_timerc                     |       S r   r    )tpr0   tws    r"   r   z#_backfill_markers.<locals>.<lambda>  s    2gr? r!   )r[  r/   r   zCallable[[], None]ro   None)r   rP  rT  rH  r  r   rd   r/   rl   rm   rB  r=  r   r   r2  r;   r   r<   r=   r9   r   )r0   rX   rQ   rM   r   rU  rV  rW  rS   rT   r   metar\  r   rO  timer_end_isotimer_payloadr`  ra  rj  s   `    `  ``       @@@r"   _backfill_markersro    s   & 
	424B#/5IB%17LB#G-=xHD/9%/PVYZ_V`D	

4>I0%gxK B0>D#BC ""B6'$)HI ""B6%'FG 	) 
!%: 0	
 	=M"JKKE  -s3  8C5!12""%DFs   D9 9	F?FFF)rU  r   r   r   r   r   rW  rV  c                  t        j                         j                  }	t               }
t	        | |||||      }t        |      \  }}g }g }i }|t        j                  k(  rt        j                  d|        nY|t        j                  t        j                  hv rt        | |	||||||||
      }n!t        j                  d| |j                         t        | |||||| |
|	|
      S )uo  idempotent reconcile.

    - state == FINALIZED → no-op
    - state == MERGED_PENDING_RECONCILE + apply=True → backfill with evidence metadata
    - state == STUCK_NEEDS_RECONCILE + apply=True → reason별 backfill
    - apply=False → actions_planned에만 기록, 실제 변경 없음
    - 동일 reconcile 반복 호출 → 동일 state, no-op (멱등)
    r  z task=%s already FINALIZED, no-op)r   rU  rV  rW  rS   rT   z$task=%s state=%s, no backfill neededrc   )uuiduuid4hexr   r*  r;  r   r   r   infor   r   ro  debugrd   rL   )r0   rU  r   r   r   r   r   rW  rV  rX   rW   rQ   rM   rP   rS   rT   rY   s                    r"   	reconcilerv    s   ( zz|''
I%)!H )2E;!M!#O(*(((6@	//,, 
 .)#%'+
 	;WekkR#'	)+ r!   )r   r   rU  r   c                   | xs
 t               }|t        z  }g }	 |j                         rP|j                  d      }t	        j
                  |      }t        |j                  di       j                               }|st        j                  d       g S g }
|D ]o  }d}|d
d} ||      }	 t        |||||      }|j                  t        j                   t        j"                  t        j$                  hv r|
j'                  |       q |
S # t        $ r }	t        j                  d|	       Y d}	~	d}	~	ww xY w# t        $ r!}	t        j)                  d	||	       Y d}	~	d}	~	ww xY w)u:   task-timers.json 전체 순회 → STUCK인 것만 보고.r   r   r   z/scan_stuck: failed to load task-timers.json: %sNz.scan_stuck: no tasks found in task-timers.jsonc                     d fd}|S )Nc                V            }|j                  di       j                  | i       S )Nr   )r   )r0   
all_timerstls     r"   _adapterz3scan_stuck.<locals>._make_adapter.<locals>._adapterW  s(    !#J%>>'26::7BGGr!   )r0   r/   ro   rp   r    )r{  r|  s   ` r"   _make_adapterz!scan_stuck.<locals>._make_adapterV  s    H  r!   )rU  r   r   r   z'scan_stuck: reconcile failed for %s: %s)r{  zCallable[[], dict]ro   zCallable[[str], dict])r   r   r   r   rl   r   rr   r   keysr   r   r   rt  rv  rM   r   r   r   r   r2  r0  )r   r   rU  r   r   r   task_idsr   r   r   stuck_reportstidadapted_loaderr}  reports                  r"   
scan_stuckr  8  sp    
	424B((KHO'''9C::c?DDHHWb16689H DE	+-M N:># 
 +<8N	N!+F ||4477((  
 $$V,1N8 K  OH#NNOD  	NLLBCMM	Ns1   A D ,AD8	D5D00D58	E"EE"c                "    | j                   rdS dS )u   automation_contracts.SmokeResult → lifecycle smoke_status string.

    expects post_merge_smoke_runner.SmokeResult.passed →
    smoke_status='PASS' if True else 'FAIL'
    r  r  )passed)srs    r"   smoke_result_to_statusr  u  s     YY6*F*r!   c                   | j                   t        j                  k(  rd}d}d}n\| j                   t        j                  k(  rd}d}t        j
                  }n*| j                   t        j                  k(  rd}d}d}nd}d}d}t        || j                  D cg c]  }|j                  j                   c}|| || j                  | j                  | j                  d	      S c c}w )
u   LifecycleReport를 AutomationDecision으로 매핑 (자동화 의사결정 contract 연동).

    회장 §6: CanonicalWorkspace + automation_contracts 연동 필수.
    NO_OPFNESCALATETBACKFILLMONITOR)r0   rX   rW   )decisionreason_codescritical_escalation_typeauto_handledrequires_chairaudit)rM   r   r   r   r   POST_MERGE_SMOKE_FAILEDr   r   rP   rI   rd   r0   rX   rW   )r  decision_strr  criticalcs        r"   build_automation_decisionr  ~  s    
 ||~///59	11	1!)AA	@@	@! .4.@.@AahhnnA!)((%~~ & 7 7))
 As   C!
)r1   c                   | j                   t        j                  k7  rydj                  d | j                  D              xs d}t        | j                  |t        j                  |dg dd| j                               S )	u   ESCALATED state에 대해 EscalationPacket 생성. 그 외 None.

    ⚠️ lifecycle은 일반적으로 ESCALATED를 직접 발동하지 않으므로,
    이 헬퍼는 explicit caller가 호출해야 함.
    Nz; c              3  f   K   | ])  }|j                   j                   d |j                    + yw)z: N)rI   rd   rJ   )r   re   s     r"   r   z*build_escalation_packet.<locals>.<genexpr>  s-      .0299??
2bii[)s   /1z"lifecycle ESCALATED state detecteduH   lifecycle state is ESCALATED — human review required before proceeding)'Review stuck_cases and resolve manuallyz0Run reconcile --apply after resolving root causez!Escalate to chair if unresolvabler  )r0   r1   escalation_typerI   why_auto_cannot_continuesafe_optionsrecommended_optionrQ   )
rM   r   r   joinrP   r   r0   r   r  r`   )r  r1   stuck_detailss      r"   build_escalation_packetr    s     ||~///II 4:4F4F  .	-  .FFV

 E((* r!   c                 J   t        j                  d      } | j                  d      }|j                  ddd       |j                  d	dd
       | j                  dd       | j                  dddd       | j                  ddddd       | j                  dd d       | S )NuW   lifecycle_reconciliation_manager — idempotent task lifecycle reconcile (task-2518 P0))descriptionT)requiredz--reconcile
store_truez,Reconcile a single task (requires --task-id))actionhelpz--scan-stuckz8Scan all tasks in task-timers.json and report stuck onesz	--task-idz/task-NNNN identifier (required for --reconcile))r  z--applyFz*Actually apply backfill (default: dry-run))r  defaultr  r   output_jsonzOutput as JSON (default: True))r  destr  r  z--workspace-rootzOverride workspace root path)r  r  )argparseArgumentParseradd_mutually_exclusive_groupadd_argument)r   groups     r"   _build_cli_parserr    s    A	A **D*9E	;  
 
G  
 NN;%VNWNN9	   NN-   NN+  
 Hr!   c           	        t               }|j                  |       }d }|j                  rt        |j                        }|j                  rZ|j
                  s|j                  d       	 t	        |j
                  |j                  |      }t        |j                                y |j"                  rW	 t#        ||j                        }|D cg c]  }|j%                          }}t        t        j                  |dd	             y y # t        $ rc}t        t        j                  t        |      |j
                  d      t        j                         t        j                   d       Y d }~y d }~ww xY wc c}w # t        $ rX}t        t        j                  d
t        |      i      t        j                         t        j                   d       Y d }~y d }~ww xY w)Nz--reconcile requires --task-id)rU  r   )r0  r0   )file   )r   rU  rh   Fri   r0  )r  
parse_argsr   r	   rv  r0   r0  rU  printrn   r   rl   rm   r/   sysstderrexitr  rf   )	argvparserr|   r   r  r   reportsr   outputs	            r"   	_cli_mainr    sb    FT"D%)Nd112~~||LL9:		jj-F
 &.."#
 
		 -jjG ,33aaiik3F3$**VAEBC 
	  	$**s3xDLLIJQTQ[Q[\HHQKK	 4 	$**gs3x01

CHHQKK	sD   );D	 2E= E8$#E= 		E5AE00E58E= =	GAGG__main__z#%(levelname)s %(name)s: %(message)s)levelformat)r|   rR   rt   r2   ro   subprocess.CompletedProcess)r|   rR   rt   zOptional[str | Path]r   Optional[RunnerType]ro   r  rq   )r   r	   ro   r	   )r   r	   r0   r/   r   r/   ro   r	   )ro   r	   )r0   r/   r   r  ro   r   )
r0   r/   r   r  r   Optional[Path]r   Optional[Callable[[str], dict]]ro   rp   )r4   r/   r   r  r   r  ro   r5   )r1   intr   r  r   r  ro   r2   )r0   r/   r   r  r   r  ro   rp   )r0   r/   r   r  ro   rp   )rx   r/   ro   r5   )r0   r/   r   r  ro   rp   )r0   r/   r   r  r   r  ro   rp   )r0   r/   r   r  r   r  r   r  r   r  r   r  ro   r.   )rQ   r.   ro   rO   )rQ   r.   ro   z&tuple[LifecycleState, list[StuckCase]])r0   r/   rQ   r.   r   r  ro   rk  )r0   r/   rX   r/   rQ   r.   ro   rp   )rO  r	   r%  r/   ro   rk  )r0   r/   rS  rp   ro   rk  )r0   r/   rX   r/   rQ   r.   rM   r   r   r  rU  r5   rV  %Optional[Callable[[Path, str], None]]rW  %Optional[Callable[[str, dict], None]]rS   rR   rT   rR   ro   rp   )r0   r/   rU  r5   r   r  r   r  r   r  r   r  r   r  rW  r  rV  r  ro   rL   )
r   r  r   r  rU  r5   r   zOptional[Callable[[], dict]]ro   zlist[LifecycleReport])r  r   ro   r/   )r  rL   ro   r   )r  rL   r1   r  ro   zOptional[EscalationPacket])ro   zargparse.ArgumentParserr   )r  zOptional[list[str]]ro   rk  )Tr   
__future__r   r  rl   loggingrz   r  rq  dataclassesr   r   r   r   enumr   pathlibr	   typingr
   r   r   __file__resolverL  _HEREr/   rO  insert"utils.canonical_workspace_resolverr   r   utils.automation_contractsr   r   r   r   	getLoggerr   r   CompletedProcess
RunnerTyper   r   r   r   r   r   r$   r.   rH   rL   r}   r   r   r   r   r   r   r   r   r   r   r   r   r
  r  r*  r5  r;  rB  rH  rP  rT  ro  rv  r  r  r  r  r  r  basicConfigINFOr    r!   r"   <module>r     s  < #     
  ( '   * * 	X ''..u:SXXHHOOAs5z"  
		8	$
 c:6667
/0  !GH " -  (, $	S$ 	
4#t 
4" " " ">   
 &H &H &H` 
 
 !	" !%#'	?
? 
? !	?
 !?2-? HL 
4 $(%)15(( !( #	(
 /( 
(\ $(%)	 ! #	
 
2 $(%)	   !  #	 
  L &*48	 # 2	
 
< &* # 
	B, (,66 %6 
	6x $(%)	## !# #	#
 
#Z &*#''+1548mm #m !	m
 %m /m 2m mhcTG&d &*	 
 
 
 #	 

 
 
N   
	:/

]& &*9=:>JJJ  J 	J #J J 7J 8J J J 
Jh %)#''+1548:>9=GG G #	G
 !G %G /G 2G 8G 7G G` &*#'156"6 !6 	6
 /6 6z+!J 23+.J%P F zGgll3XYK r!   