
    (y#j[e                       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	 ddl
m
Z
mZmZ ddlmZ ddlmZ ddlmZmZmZ d	Zd
ZdZdZdZdZdZdZdZdZdZdZ dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ* e ed             Z+dZ,d!Z-d"Z.dZ/d#Z0e	 G d$ d%             Z1efdd&d=d'Z2efdd&d>d(Z3d?d)Z4d@d*Z5dAd+Z6dBd,Z7dCdDd-Z8dEd.Z9dFd/Z:dGd0Z;eeeddd1	 	 	 	 	 	 	 	 	 dHd2Z<edddddd3dddd4dddddd5	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dId6Z=efdddddd3dddddd7dddddd8	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dJd9Z>dCdKd:Z?g d;Z@eAd<k(  r eB e?             y)Lu  dispatch.anu_pickup_driver — task-2721 P0-b user-level systemd path driver.

default DISABLED. activation flag(memory/state/p0b_driver_enabled == "enabled") 부재 시 전면 no-op.
scan 한정: memory/events/task-*.result.json final 만. tmp/partial/다른 marker·jsonl·md → NOOP_NOT_TARGET.
6조건 전부 PASS 시에만 P0-a pickup_once(lock-free) 호출. pickup_once 가 ANU-owned wake argv(dry-run)를 빌드.
실제 cron 발사 0(P0-a dry_run=True/FIRE_NOT_ACTIVATED). ANU key literal 0 — .env.keys 런타임 로드만.
    )annotationsN)	dataclass)datetime	timedeltatimezone)Optional)pickup_once)CANONICAL_ROOTVERDICT_AUTHORITATIVEverify_collector_authoritativezsystemd-pathzmemory/state/p0b_driver_enabledzmemory/eventszmemory/p0b_state/quarantinezmemory/p0b_state/processedz"memory/p0b_state/driver_runs.jsonlztask-*.result.jsong       @   g?
WAKE_BUILTPICKUP_SKIP
QUARANTINEFIRE_FAILEDNOOP_DISABLEDNOOP_NOT_TARGETNOOP_NOT_READYANUFOREIGNSELFenableddisabled	   )hoursSKIP_TERMINALSKIP_DEDUPEPENDING_OWNER_PROOFc                      e Zd ZU ded<   ded<   ded<   dZded<   dZd	ed
<   dZded<   dZded<   dZded<   dZ	ded<   e
Zded<   eZded<   ddZy)DriverRecordstrtsresult_pathverdictNOptional[str]owner_key_classFboolquarantinedquarantine_reasonfire_cron_idr   intretryerrordriver
activationc                    | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                  | j                  | j                  dS )Nr"   r#   r$   r&   r(   r)   r*   r,   r-   r.   r/   r1   )selfs    1/home/jay/workspace/dispatch/anu_pickup_driver.pyto_jsonzDriverRecord.to_jsonW   sc    ''++||#33++!%!7!7 --ZZZZkk//
 	
    )returndict)__name__
__module____qualname____annotations__r&   r(   r)   r*   r,   r-   DRIVER_NAMEr.   ACTIVATION_DISABLEDr/   r4    r5   r3   r    r    I   sg    GL%)O])K'+}+"&L-&E3NE=FC)J)
r5   r    flag_readerc                  |g	  |       }|yt        |      j                         r*t        |      j                         d   j                         S t        |      j                         S t        j
                  j                  | t              }	 t        |dd      5 }|j                         }ddd       j                         S # t         $ r Y yw xY w# 1 sw Y   (xY w# t        t        f$ r Y yw xY w)u   flag 파일 첫 줄 trim 값 반환. 부재/읽기실패 → "" (=> disabled).
    flag_reader: 테스트 주입용 callable() -> Optional[str] (None 이면 실제 파일 읽기).N r   rutf-8encoding)	Exceptionr!   strip
splitlinesospathjoinACTIVATION_FLAG_RELopenreadlineOSError
ValueError)rootr@   val	flag_pathfhfirsts         r3   read_activationrW   h   s     	-C ;36s8>>3Cs3x""$Q'--/YSIYYT#67I)S73 	"rKKME	" ;;=  			" 	"Z  s;   C C! C.C! 	CCCC! !C32C3c               *    t        | |      t        k(  S )Nr?   )rW   ACTIVATION_ENABLED)rR   r@   s     r3   is_activatedrZ   |   s    4[9=OOOr5   c                    | syt         j                  j                  t        |             }|j	                  d      xr |j                  d      S )u   final task-*.result.json 만 True. basename 이 'task-' 로 시작 + '.result.json' 으로 끝.
    '.result.json.tmp-...'/partial/.md/.jsonl/다른 marker → False.Fztask-z.result.json)rJ   rK   basenamer!   
startswithendswith)rK   bases     r3   	is_targetr`      s?     77CI&D??7#En(EEr5   c                ,     |        j                         S N)	isoformat)clocks    r3   _now_kstre      s    7r5   c                |    	 t        | j                  d            rt        S 	 t        S # t        $ r	 t        cY S w xY w)uO   envelope claim 으로 SELF/FOREIGN 라벨 추정 (판정 아님, 라벨링만).self_key_used)r'   getOKC_SELFAttributeErrorOKC_FOREIGN)envelopes    r3   _envelope_claim_classrm      sA    _-.O /   s   ) ;;c                    t         j                  j                  | |      }t         j                  j                  |      s|S t	        t        t        j                         dz              }t         j                  j                  | | d|       }d}t         j                  j                  |      rM|dz  }t         j                  j                  | | d| d|       }t         j                  j                  |      rM|S )uY   dest_dir/basename. 이미 존재하면 .{ms타임스탬프}[-n] suffix 로 충돌 회피.i  .r      -)rJ   rK   rL   existsr!   r+   time)dest_dirr\   destr"   candns         r3   _collision_safe_destrx      s    77<<(+D77>>$	St#$	%B77<<XJat"45D	A
''..
	Qww||H
!B4q&<= ''..
 Kr5   c                   |xs$ t         j                  j                  |t              }	 t        j                  |d       t        |t         j                  j                  |             }	 t        j                  | |       y# t        $ r}d| cY d}~S d}~ww xY w# t        $ rG 	 t        j                  | |       Y y# t        t        j                  f$ r}d| cY d}~cY S d}~ww xY ww xY w)u  terminal(WAKE_BUILT/PICKUP_SKIP) result 파일을 watched 밖 processed 디렉토리로
    atomic 이동. 성공 시 None, 실패 시 에러 메시지(str) 반환(fail-safe: 크래시 0).
    os.replace(같은 fs atomic) 우선, 실패 시 shutil.move fallback.Texist_oku   processed move 실패: N)rJ   rK   rL   PROCESSED_DIR_RELmakedirsrx   r\   rP   replaceshutilmoveError)rK   rR   processed_dirpdirru   excs         r3   _move_processedr      s     ABGGLL/@AD/
D4(#D"''*:*:4*@A3


4	  /(../
  3	3KKd#& 	3,SE22	3	3sT   A B +B 	BBBB	C-'B??C)C$C)C-$C))C-c                X   |xs$ t         j                  j                  |t              }	 t        j                  |d       t        |t         j                  j                  |             }t        j                  | |       y# t        t        j                  f$ r}d| cY d}~S d}~ww xY w)ud   result 파일을 quarantine 디렉토리로 이동. 실패 시 예외 메시지 반환(None=성공).Trz   Nu   quarantine move 실패: )rJ   rK   rL   QUARANTINE_DIR_RELr}   rx   r\   r   r   rP   r   )rK   rR   quarantine_dirqdirru   r   s         r3   _quarantine_mover      s    CRWW\\$0BCD0
D4(#D"''*:*:4*@AD$V\\" 0)#//0s   AB B)B$B)$B)c                   |xs$ t         j                  j                  |t              }	 t        j                  t         j                  j                  |      d       t        |dd      5 }|j                  t        j                  | j                         d      dz          |j                          t        j                  |j                                d	d	d	       y	# 1 sw Y   y	xY w# t        $ r Y y	w xY w)
uM   DriverRecord.to_json() 한 줄 JSON append. ANU key literal 절대 미기록.Trz   arD   rE   F)ensure_ascii
N)rJ   rK   rL   EVIDENCE_JSONL_RELr}   dirnamerN   writejsondumpsr4   flushfsyncfilenorP   )recordrR   evidence_pathevrU   s        r3   _append_evidencer      s    		@"'',,t-?@B
BGGOOB'$7"cG, 	"HHTZZ 0uELMHHJHHRYY[!	" 	" 	"  s1   AC, ,A+C C,  C)%C, )C, ,	C87C8c                   |xs" t         j                  j                  |ddd      }t         j                  j                  |      sy	 t	        |dd      5 }|D ]o  }|j                         }|s	 t        j                  |      }t        |t              s=|j                  d      d	k(  sR|j                  d
      | k(  sg ddd       y 	 ddd       y# t        t        f$ r Y w xY w# 1 sw Y   yxY w# t        t        f$ r Y yw xY w)uL   dedupe ledger 에 동일 task_id 의 PICKUP_WAKE_BUILT 항목 존재 여부.memoryeventszcallback_4tuple_index.jsonlFrC   rD   rE   eventPICKUP_WAKE_BUILTtask_idNT)rJ   rK   rL   isfilerN   rH   r   loadsrQ   	TypeError
isinstancer7   rh   rP   )r   ledger_pathrR   ledgerrU   lineentrys          r3   _dedupe_hitr      s    BGGLLh"?F 77>>&!&#0 	 B  zz| JJt,E ud+		'*.AA		),7	  	  	 "  #I. 	 "  Z  sr   C6 C*0CC*C*+C* C*C6 
C*C6 C'$C*&C''C**C3/C6 3C6 6DD)
stable_secretriesintervalsleep_fnstat_fnc                  |xs t         j                  }|xs t        j                  }g }t	        t        d|            D ]^  }	  ||       }	|j                  |	j                  |	j                  f       t        |      dk\  r|d   |d   k(  r n||dz
  k  sW ||       ` |d   d   }
 |       j                         |
z
  }||k  ryt        |      dk\  r|d   |d   k7  ryy# t        $ r Y  yw xY w)	u  write race 방어 readiness 판정. (ready: bool, reason: str) 반환.

    판정 규칙:
    - 파일 stat 실패(존재X 등) → (False, "stat_fail"): DEFER, 다음 트리거 재평가.
    - mtime 이 now - stable_sec 이내(최근 생성/수정) → (False, "recent_mtime"): writer 미완 가능 → DEFER.
    - size/mtime 안정성: 짧은 간격으로 최대 retries 회 stat 하여 (size, mtime) 불변 확인.
      마지막 두 샘플이 다르면 (False, "unstable"): 아직 쓰는 중 → DEFER.
    - 위 모두 통과(aged + stable) → (True, "ready").
    단순 무한 sleep 없음: 총 window = (retries-1) * interval ≤ 약 0.6s.
    rp   )F	stat_fail   )Frecent_mtime)Funstable)Tready)rJ   statrs   sleeprangemaxrP   appendst_sizest_mtimelen	timestamp)rK   rd   r   r   r   r   r   samplesattemptstmtimeages               r3   _check_readinessr      s    (  G%4::HGQ) 
	(B 	

BKK01w<1!;Wq[ X
 BKNE
'



%C
Z&
7|qWR[GBK7"  	('	(s   C""	C/.C/rB   F)rR   	pickup_fnlauncher_fn	verify_fnproberd   executor_keyr   r   r   write_evidencer   r   readiness_retriesreadiness_intervalr   c                  	*+ |xs t         }|xs t        }|xs d }t        |      +d6fd*	 	 d7	 	 	 d8* 	+fd}t               s *t	        + t
        t                    S t         ||t        n||t        n||t        n||      \  }}|s *t	        + t        d|t                    S 	 t        j                  j                         }|dk  r |d	      S 	 t!         d      5 }|j#                         }ddd       dv r |d      S 	 t%        j&                  |j)                  d            }t/        |t0              s |d      S |j3                  d      }|j3                  d      }t/        |t4              r|j7                         s |d      S t/        |t4              r|j7                         s |d      S |j7                         }t        j                  j9                  |      |k7  sd|v sd|v sd|v r |d      S |j3                  d      }t/        |t0              s |dt:              S |j3                  d      xs |j3                  d      }	  |||||| |             }tA        |d"d      tB        k7  r |d#t?        |            S tD        }t        j                  jG                         }t        j                  jI                  || d$      } t        j                  jI                  || d%      }!t        j                  jK                  |       s,t        j                  jK                  |!      stM        ||      r-tO         |
      }" *t	        + tP        |d|"t        &            S 	  | ||'      }#tA        |#d"d      }$|$tT        k(  rd}%d}&|/	  |tA        |#d*d      |tA        |#d+d,      -      }'tA        |'d.d      }%tO         |
      }"d0jI                  d1 |&|"fD              xs d}( *t	        + tV        tD        |%|(t        2            S |$tX        tZ        fv r,tO         |
      }" *t	        + tP        ||"t        )            S |$t\        t^        fv r$ |d3t5        |$      ja                         z   |      S tA        |#d4d      })t/        |)tb              r|)rd0jI                  |)      nd5|$ }( *t	        + tR        ||(t        )            S # t        $ r} |d	d
|       cY d}~S d}~ww xY w# 1 sw Y   xY w# t        $ r} |dd|       cY d}~S d}~ww xY w# t*        t,        f$ r} |dd|       cY d}~S d}~ww xY w# t<        $ r!} |dt?        |      d | !      cY d}~S d}~ww xY w# t<        $ r,} *t	        + tR        |d(| t        )            cY d}~S d}~ww xY w# t<        $ r}d/| }&Y d}~d}~ww xY w)9up   단일 result.json 처리. activation 은 호출자(scan_once)가 이미 보장 — 여기선 target/6조건만.c                 4    t        j                  t              S rb   r   nowKSTr>   r5   r3   <lambda>zprocess_one.<locals>.<lambda>6      hll3/ r5   c                &    rt        |        | S rb   )r   )recr   rR   r   s    r3   _emitzprocess_one.<locals>._emit:  s    S$6
r5   Nc                    t              }|}|r|r|dz   |z   n|} t        	t        |d| |t                    S )N; T)r"   r#   r$   r&   r(   r)   r-   r/   )r   r    VERDICT_QUARANTINErY   )
reasonowner_class	extra_errmove_errerrr   rK   r   rR   r"   s
        r3   _quarantinez process_one.<locals>._quarantine?  sW    #D$?-03:(hC\&'$)	
 	 		r5   r"   r#   r$   r/   )rd   r   r   r   r   F)r"   r#   r$   r(   r)   r/   size0u   getsize 실패: )r   r   rb
parse_failzread:     	null_byterD   zparse: schema_failr   completion_signalz../\collector_envelopeowner_unprovable)r   schedule_id)r   rl   r   r   r   r   owner_proof_erroru   verify_fn 예외: )r   r   r$   owner_proof_failz.pickup.donez.pickup.acked)r"   r#   r$   r&   r(   r-   r/   )r   r   u   pickup_fn 예외: )r"   r#   r$   r&   r-   r/   argvsha256rB   )r   r   decisionu   launcher 예외: r   c              3  &   K   | ]	  }|s|  y wrb   r>   ).0xs     r3   	<genexpr>zprocess_one.<locals>.<genexpr>  s     ?aQ?s   )r"   r#   r$   r&   r*   r-   r/   pickup_reasonszpickup verdict=)r   r    r6   r    )NN)r   r!   r   r%   r   r%   r6   r    )2r	   r   re   r`   r    VERDICT_NOOP_NOT_TARGETrY   r   
STABLE_SECSTABILITY_RETRIESSTABILITY_INTERVAL_SECVERDICT_NOOP_NOT_READYrJ   rK   getsizerP   rN   readr   r   decoderQ   UnicodeDecodeErrorr   r7   rh   r!   rH   r\   rk   rG   rm   getattrr   OKC_ANUr   rL   rr   r   r   VERDICT_PICKUP_SKIPVERDICT_FIRE_FAILED_PICKUP_WAKE_BUILTVERDICT_WAKE_BUILT_PICKUP_SKIP_TERMINAL_PICKUP_SKIP_DEDUPE_PICKUP_QUARANTINE_PICKUP_PENDINGlowerlist),rK   rR   r   r   r   r   rd   r   r   r   r   r   r   r   r   r   r   r   r   ready_reasonsizer   rU   rawresultr   r   rl   r   vr   
result_dir	done_path
acked_pathr   respvr*   
launch_errlrr   r   r   r"   s,   ``       ` ``                             @@r3   process_oner    s    * ([I;;I0/E	%B
 ?C/3,8D $ T?\+)	
  	 +!+!3:%6%>!DU+=+E'K]E< \**)
  	Hwwt$ qy7##C$ 	'')C	 #~;''DCJJw/0
 fd#=))jj#G

#67w$=))(#.3D3J3J3L=))mmoG
 	!W,7?'>7?=)) zz./Hh%-;GG,,}-JM1JKA%#
 q)T"&;;-'<X'FH 	H K &JZG9L)ABIjWI]*CDJ
y!77>>*%wT2"4}=\'')
  	
<[Q 
i	&B	 
"7 C.#"3"5
  'r:t< #4}=ii?J#9??G4\&#%)
  	 
#%899"4}=\'')
  	 
 /229s2w}}6KPP c9d+G *7D 9g$))G
_]_\`KaC##%  a  H70@.FGGH	 	 C<VC5>BBC *+ D<WSE?CCDP  A.'<X'F'9#%?A 	AAD  \''&se,)
  	0  706
7s   <S& )T 5T	T $T9 #U" V ).W &	T/T;TT	TT 	T6T1+T61T69UUUU"	V+VVV	W!V?9W?W	W WW T)r   r   r   r   rd   r   r   r   r   pathsr@   r   r   r   r   r   r   c                  |xs d }t        | |      s2t        t        |      dt        t              }|rt        || |       |gS |
Ht        j                  j                  | t        t              }t        t        j                  |            }nt        |
      }g }|D ]M  }t        |fi d| d|d|d|d	|d
|d|d|d|d|	d|d|d|d|d|d|}|j                  |       O |S )u   진입점. (1) activation 재확인: disabled → [NOOP_DISABLED] 1건 + evidence + pickup 미호출.
    (2) enabled → 후보 경로 결정 → 각 path process_one → record 목록 반환. 각 record evidence append.c                 4    t        j                  t              S rb   r   r>   r5   r3   r   zscan_once.<locals>.<lambda>$  r   r5   r?   rB   r   rR   r   r   r   r   rd   r   r   r   r   r   r   r   r   r   r   )rZ   r    re   VERDICT_NOOP_DISABLEDr=   r   rJ   rK   rL   EVENTS_DIR_RELRESULT_GLOBsortedglobr  r  r   )rR   r   r   r   r   rd   r   r   r   r   r  r@   r   r   r   r   r   r   r   pattern
candidatesrecordsps                          r3   	scan_oncer'    sZ   . 0/E +6)*	
 S$6u }'',,t^[ADIIg./
%[
G 

  
 $	

  
 
 
 &
 $
 *
 (
 *
 (
 "
 0
   2!
" #
& 	s)* Nr5   c                "    t        t               y)uH   scan_once(CANONICAL_ROOT) 실행 후 0 반환. CLI 인자 처리 최소.r   )r'  r
   )r   s    r3   mainr)  S  s    nr5   )r<   rM   r  r   r|   r   r   r   r   r   r  r  r   r  r  r   r   r  rk   ri   rY   r=   r   r    rW   rZ   r`   r   r  r'  r)  __main__)rR   r!   r6   r!   )rR   r!   r6   r'   )rK   r!   r6   r'   )r6   r!   )rl   r7   r6   r!   )rt   r!   r\   r!   r6   r!   rb   )rK   r!   rR   r!   r   r%   r6   r%   )rK   r!   rR   r!   r   r%   r6   r%   )r   r    rR   r!   r   r%   r6   None)r   r!   r   r%   rR   r!   r6   r'   )
rK   r!   r   floatr   r+   r   r,  r6   tuple)rK   r!   rR   r!   r   r!   r   r%   r   r%   r   r%   r   r'   r   r%   r   Optional[float]r   Optional[int]r   r.  r6   r    )rR   r!   r   r!   r   r%   r   r%   r   r%   r  zOptional[list]r   r'   r   r%   r   r.  r   r/  r   r.  r6   r  )r6   r+   )C__doc__
__future__r   r"  r   rJ   r   rs   dataclassesr   r   r   r   typingr   !dispatch.anu_result_pickup_runnerr	   'dispatch.anu_owned_callback_enforcementr
   r   r   r<   rM   r  r   r|   r   r   r   r   r   r  r  r   r  r  r   r   r  rk   ri   rY   r=   r   r  r  r	  r
  r  r    rW   rZ   r`   re   rm   rx   r   r   r   r   r   r  r'  r)  __all__r8   
SystemExitr>   r5   r3   <module>r8     sI   #   	   ! 2 2  :  7  2 0 9 "
 
   " # ! # ' + )      yq!" " ' # ! ' 
 
 
< !/ t ( , PD P
F3*	0
@ #$,*
* 	*
 * * *b 

!%$(#' #'"&'+*.%j
j j j j "j !j j !j  j  %!j" (#j& 'j^ B 

!%$(#' #'"&'+*.'B
B B B "B !B B B !B   !B" %#B$ (%B( 
)BL D z
TV
 r5   