
    *jP                       d Z ddlm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 d	Zd
ZdZdZdZd"dZ G d dee      Ze G d d             Zedef   Z G d d      Ze G d d             Zd#dZd$dZ	 	 	 	 	 	 	 	 d%dZ dd	 	 	 	 	 	 	 	 	 d&dZ!d'dZ"dddddded 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d(d!Z#y))u  utils/completion_callback_fallback_cancel.py

task-2553+9a — CALLBACK_FALLBACK_CANCEL_ON_SUCCESS (회장 결정).

목적: normal completion callback collector 가 result/report/collector-result
marker 생성을 durable 하게 완료했을 때, dispatch 시점에 사전 등록된 fallback
callback cron 을 자동 제거하여 뒤늦은 redundant 발화를 없앤다.

설계 원칙 (task-2553+9a §4, §9-R.1~§9-R.5):
  * callback orchestrator(utils/anu_delegation_completion_callback.py) **무수정**.
    본 모듈은 완전 분리·독립 — orchestrator 를 import/호출하지 않는다(§9-R.4).
  * cron remove 는 §9-R.1 5조건 결합 검증 전부 충족 시에만 (오발 제거 0).
  * success gate 는 §9-R.2 durable evidence 기반 — caller boolean 단독 금지.
  * cron-remove 실행은 dependency-injected `remover` (§9-R.5). 실 subprocess
    호출은 운영 collector 만, 본 task 구현/테스트는 fake/dry-run only.

분류(CancelClassification):
  CANCELLED              fallback cron 제거 성공 → fallback_cancelled=true
  ALREADY_GONE           이미 삭제됨(또는 만료) — idempotent, 실패 아님
  ALREADY_FIRED          이미 발화함 — 기존 DUPLICATE_CALLBACK_IGNORED 경로 유지
  SKIPPED_NORMAL_FAILED  durable evidence 부재/HOLD/failure/partial → fallback 보존
  SKIPPED_UNTRUSTED      §9-R.1 5조건 중 하나라도 불충족 → remove 미실행
  REMOVE_FAILED_WARNING  remove 시도 실패 → warning marker, collector success 유지
    )annotationsN)	dataclassfield)datetimetimezone)Enum)Path)CallableOptionall   L5: c119085addb0f8b7fallback)holdhold_for_chairfailfailedfailureerrorpartialcrashkilledtimeoutaborted	cancelledcanceledrunningpendingunknown)	okpasspassedsuccess	succeededcomplete	completeddonedefensive_hold_passc                 f    t        j                  t        j                        j	                  d      S )Nz%Y-%m-%dT%H:%M:%SZ)r   nowr   utcstrftime     Z/home/jay/workspace/.worktrees/task-2686-dev8/utils/completion_callback_fallback_cancel.py_now_utcr.   I   s!    <<%../CDDr,   c                  $    e Zd ZdZdZdZdZdZdZy)CancelClassification	CANCELLEDALREADY_GONEALREADY_FIREDSKIPPED_NORMAL_FAILEDSKIPPED_UNTRUSTEDREMOVE_FAILED_WARNINGN)	__name__
__module____qualname__r1   r2   r3   r4   r5   r6   r+   r,   r-   r0   r0   M   s#    I!L#M3+3r,   r0   c                  8    e Zd ZU dZded<   dZded<   dZded<   y)	RemoverResultuN   cron remover 호출 결과. status: removed|already_gone|already_fired|failed.strstatus detailNOptional[dict]raw)r7   r8   r9   __doc____annotations__r?   rA   r+   r,   r-   r;   r;   V   s    XKFCCr,   r;   .c                  V    e Zd ZdZej
                  j                  dd      ZddddZy)	RealCokacdirCronRemoverun  실 `cokacdir --cron-remove` wrapper (운영 collector 전용 기본값).

    본 task 의 regression 은 fake remover 를 주입하므로 이 클래스의 subprocess
    경로는 테스트에서 절대 실행되지 않는다(§9-R.5). dry_run=True 면 subprocess
    호출 자체를 하지 않고 시뮬레이션 결과만 돌려준다(이중 안전장치).
    COKACDIR_BINz/usr/local/bin/cokacdirTdry_runc          	     \   |rt        ddd|d      S t        j                  | j                  d|dt	        t
              dt        gddd	
      }	 t        j                  |j                  j                         xs d      }|j                  d      dk(  rt        dd|      S t	        |j                  dd            j                         }d|v sd|v sd|v rt        d||      S t        d|xs d|      S # t        j                  $ r  d|j                  j                         d}Y w xY w)NremoveduE   dry-run: 실 subprocess 호출 0 (운영 collector 만 실제 제거)T)rH   cron_idr=   r?   rA   z--cron-removez--chatz--key<   )capture_outputtextr   z{}r   )r=   messager=   r   zcokacdir okrP   r>   z	not foundzno suchalreadyalready_goner   zremove failed)r;   
subprocessrunbinaryr<   ANU_CHAT_IDANU_KEYjsonloadsstdoutstripJSONDecodeErrorgetlower)selfrK   rH   procpayloadmsgs         r-   __call__z RealCokacdirCronRemover.__call__m   s.     ^ $9 
 ~~K   
	Jjj!2!2!4!<=G ;;x D( 	-WUU'++i,-335#c!1Y#5E sPPHS5KOQXYY ## 	J!(T[[5F5F5HIG	Js   1C8 80D+*D+N)rK   r<   rH   boolreturnr;   )	r7   r8   r9   rB   osenvironr]   rU   rc   r+   r,   r-   rE   rE   c   s'     ZZ^^N,EFF8< Zr,   rE   c                      e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<    ee
      Z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dZy)CancelDecisionr0   classificationr<   task_idtarget_cron_idrd   cron_remove_invokedfallback_cancelledcancel_skipped_reason)default_factorydictsafe_remove_checksdurable_evidenceNr@   remover_resultlisthold_reasonsnotesr>   ts_utcc                &   d| j                   | j                  | j                  j                  | j                  | j
                  | j                  | j                  | j                  | j                  | j                  | j                  | j                  dS )N"callback_fallback_cancel_result_v1)schemark   rl   rj   rm   rn   ro   rr   rs   rt   rv   rw   rx   )rk   rl   rj   valuerm   rn   ro   rr   rs   rt   rv   rw   rx   )r_   s    r-   to_dictzCancelDecision.to_dict   s{    :||"11"1177#'#;#;"&"9"9%)%?%?"&"9"9 $ 5 5"11 --ZZkk
 	
r,   )re   rq   )r7   r8   r9   rC   r   rq   rr   rs   rt   ru   rv   rw   rx   r}   r+   r,   r-   ri   ri      s|    ((L$T::"48d8%)NN)t4L$4-E4-FC
r,   ri   c                    	 t        j                  | j                  d            S # t        t         j                  f$ r Y y w xY w)Nutf-8encoding)rX   rY   	read_textOSErrorr\   )paths    r-   
_read_jsonr      s>    zz$..'.:;;T))* s   $' AAc                    | xs dj                         j                         syt        D ]  }|v s y t        fdt        D              S )Nr>   Fc              3  4   K   | ]  }|k(  xs |v   y w)Nr+   ).0tokss     r-   	<genexpr>z%_status_is_success.<locals>.<genexpr>   s!     Fsax#3!8#Fs   )r[   r^   _FAILURE_STATUS_TOKENSany_SUCCESS_STATUS_TOKENS)r=   r   r   s     @r-   _status_is_successr      sQ    	2$$&A% !8 F/EFFFr,   c                "   dddddddd}t        |       }|du|d<   |j|j                  d      xs; |j                  d      xs( |j                  d      xs |j                  d	      xs d}||d
<   t        t        |            |d<   |j	                         xr |j                         j                  dkD  |d<   |j	                         |d<   |d   sd|d<   |S |d   sd|d
   d|d<   |S |d   sd|d<   |S |d   sd|d<   |S d|d<   d|d<   |S )u   §9-R.2: result.json(존재 AND status 성공/비-HOLD/비-failure) + report +
    collector-result marker 가 실재·정합할 때만 success. boolean 은 권위 아님.FNr>   )result_json_existsresult_json_statusresult_json_status_okreport_existscollector_result_marker_exists	satisfiedreasonr   r=   rj   final_statusresultr   r   r   r   r   u8   result.json 부재 → normal collector 미완료/실패r   u,   result.json status 비-성공/HOLD/failure ()u"   report 부재 또는 비어 있음u   collector-result marker 부재Tr   u5   durable evidence 정합 (result+status+report+marker))r   r]   r   r<   existsstatst_size)result_json_pathreport_pathcollector_result_marker_pathevrjr=   s         r-   evaluate_durable_evidencer      s    $"!&*/B 
$	%B!~B	~FF8 vv&'vvn% vvh  	 $* &8V&E"#?!1!1!3!;!;a!?  ,H+N+N+PB'("#Q8 I '(:2>R;S:VVWX 	8 I  ;8 I 0178 I ;N8Ir,   )callback_contractc                "   ddddddddddd
}t        |      }|d|d<   |S d|d	<   |j                  d
      }t        |t              sd|d<   |S |j                  d      }||d<   t	        |xr t        |t
              xr ||k(        |d<   |j                  d      | k(  |d<   |j                  d      t        k(  xr |j                  d      t        k(  |d<   |j                  d      t        k(  |d<   t	        |      xr |d   |d<   |.|j                  d      }|d|d<   n||k(  rd|d<   n
d|d<   d|d<   t        |d   |d   |d   |d   |d   f      |d<   |d   s+dD 	cg c]	  }	||	   s|	 }
}	ddj                  |
      z   |d<   |S c c}	w )u   §9-R.1 5조건. dispatch-fired marker 의
    callback_policy_a.fallback_callback_cron_id 가 단일 권위.
    callback contract 는 동일값 교차확인용 보조일 뿐 단독 권위 아님.FNzn/ar>   )
c1_marker_id_matchesc2_task_bindingc3_ownershipc4_role_fallbackc5_not_stale_or_typoall_satisfiedmarker_presentauthority_cron_idcontract_cross_checkfail_reasonuQ   dispatch-fired marker 부재/파싱불가 → 추정 remove 0 (SKIPPED_UNTRUSTED)r   Tr   callback_policy_auB   marker.callback_policy_a 부재 → fallback_cron_id 권위 없음fallback_callback_cron_idr   r   rk   r   chat_idanu_keyr   fallback_roler   r   contract_no_fallback_idr   matchMISMATCHr   )r   r   r   r   r   u   5조건 미충족: ,)r   r]   
isinstancerq   rd   r<   rV   rW   FALLBACK_ROLEalljoin)rk   rl   dispatch_fired_marker_pathr   checksmarkerpolicyauthority_idcontract_idkr   s              r-   evaluate_safe_remover      sH    !& ! %! %F 23F~_ 	} #FZZ+,Ffd#P 	} ::9:L".F &* 	+|S)	+N*&F!" !'

9 5 @F 	

9, 	-JJy!W, > 	

?#}4  &*,%7 &F=F!"
 $'++,GH-FF)*L(-4F)*-7F)*-2F)*!)*$%>"%&)*	
F? /"

 !9 

 

 !68H H}M

s   #Fc                   	 | j                   j                  dd       t        j                  t	        |       t        j
                  t        j                  z  t        j                  z  d      }t        j                  |dd      5 }|j                  t        j                  t               t        j                         d             d	d	d	       y# t        $ r Y yw xY w# 1 sw Y   yxY w)
u   O_CREAT|O_EXCL atomic — normal-success-cancel vs fallback-fire race 에서
    단일 처리만 허용(이중 처리·재escalate 0).Tparentsexist_oki  Fwr   r   )	locked_atpidN)parentmkdirrf   openr<   O_CREATO_EXCLO_WRONLYFileExistsErrorfdopenwriterX   dumpsr.   getpid)	lock_pathfdfhs      r-   _acquire_cancel_lockr   ^  s    td;WWS^RZZ"))%;bkk%I5Q 
2sW	- L
(*RYY[IJKL	  Ls   A-C AC!	CC!C*FT)fallback_cancelled_marker_pathcancel_lock_pathr   normal_collector_successremoverrH   now_fnc                t   |

t               }
 |       }t        |||      }t        |	      |d<   |d   s)t        t        j
                  | |ddd|d    |dg|	      S t        | |||	      }|d
   s]g }|d   s|j                  d       |d   dk(  r|j                  d       t        t        j                  | |ddd|d    d|||dg|      S |-t        |      s"t        t        j                  | |ddd|||	      S  |
||      }|j                  |j                  |j                  d}|j                  dk(  rv|O|j                  j                  dd       |j!                  t#        j$                  d| |d||||ddd      d !       t        t        j&                  | |ddd"|||d#g|$      S |j                  d%k(  r#t        t        j(                  | |ddd&||||'
      S |j                  d(k(  r#t        t        j                  | |ddd)||||'
      S t        t        j*                  | |ddd*|||d+g|$      S ),u   normal collector 성공 시 사전등록 fallback callback cron 자동 제거.

    `normal_collector_success` 는 보조 신호일 뿐 §9-R.2 durable-evidence gate 에
    종속한다 — 단독으로 cancel 을 결정하지 않는다.
    )r   r   r   caller_boolean_auxr   FuW   normal collector durable evidence 미충족 → fallback 보존 (예정대로 발화): r   us   §9-R.2: caller boolean 은 권위 아님 — durable evidence 부재 시 boolean=true 라도 SKIPPED_NORMAL_FAILED)	rj   rk   rl   rm   rn   ro   rs   rw   rx   )rk   rl   r   r   r   r   u?   dispatch-fired marker 부재 — fallback_cron_id 신뢰 불가r   r   uB   callback contract vs marker 권위 id MISMATCH — 타 cron 위험u@   §9-R.1 5조건 결합 검증 실패 → cron remove 미실행 (r   r   uQ   오발 제거 0: 신뢰할 수 없는 fallback_cron_id 는 절대 추정 remove 0)rj   rk   rl   rm   rn   ro   rr   rs   rv   rw   rx   u   동시 cancel 락 획득 실패 — 다른 처리기가 단일 처리 중 (이중 처리·재escalate 0, DUPLICATE 경로 유지))	rj   rk   rl   rm   rn   ro   rr   rs   rx   rG   rL   rJ   Tr   fallback_cancelled_v1)r{   rk   r   rn   rH   rx   rr   rs      )ensure_asciiindentr   r   r>   z&fallback_cancelled=true marker persist)rj   rk   rl   rm   rn   ro   rr   rs   rt   rw   rx   rR   u@   fallback cron 이미 삭제/만료 — idempotent, 실패 아님)
rj   rk   rl   rm   rn   ro   rr   rs   rt   rx   already_fireduJ   fallback 이미 발화 — 기존 DUPLICATE_CALLBACK_IGNORED 경로 유지u   cron remove 실패 — warning marker. normal collector success 는 실패로 바꾸지 않음 (§3.5). fallback 은 DUPLICATE 경로로 음소거됨z2collector success preserved despite remove failure)rE   r   rd   ri   r0   r4   r   appendr5   r   r3   r=   r?   rA   r   r   
write_textrX   r   r1   r2   r6   )rk   rl   r   r   r   r   r   r   r   r   r   rH   r   tsr   r   rv   rrremover_dicts                      r-   cancel_fallback_on_successr   n  s   * )+	B 
#)%A
B
  $$<=Bk?/EE) %$**,X,9  < 
 	
& "%#=+	F /"&'Q ()Z7T /AA) %$=)*!-  &%c !
 	
( #,@AQ,R/==) %$J  &
 	
  
	1B ii299RVVLL	yyI)5*1177t7T*55

"9#*5C.2#*"$.4,.	 "' ! 6 " /99) $#"$%';<
 	
 
yyN"/<<) $$"d%'
 	
 
yyO#/==) $$\%'
 	
  +AA%  ` "#CD r,   )re   r<   )r   r	   re   r@   )r=   r<   re   rd   )r   r	   r   r	   r   r	   re   rq   )
rk   r<   rl   r<   r   r	   r   r@   re   rq   )r   r	   re   rd   )rk   r<   rl   r<   r   r	   r   r	   r   r	   r   r	   r   Optional[Path]r   r   r   r@   r   rd   r   zOptional[Remover]rH   rd   r   zCallable[[], str]re   ri   )$rB   
__future__r   rX   rf   rS   dataclassesr   r   r   r   enumr   pathlibr	   typingr
   r   rV   rW   r   r   r   r.   r<   r0   r;   RemoverrE   ri   r   r   r   r   r   r   r+   r,   r-   <module>r      s  0 #  	  ( '   % 
 $
 E43 4    3%
&(Z (ZV 
 
 
FG// / #'	/
 
/t )-__ _ !%	_
 &_ 
_J
0 6:'+(,%*!% (|| | !%	|
 | | #'| %3| %| &| #| | | | |r,   