
    +j6Q              
         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mZ ddl	m	Z	m
Z
mZ ddlmZmZmZ ddlmZmZ dZd	Zd
ZdZdZdZdZeefZefZdZdZdZdZdZ  e e
d            Z!e G d d             Z"d-dZ#d.dZ$d/d0dZ%d1dZ&d2dZ'd3d4dZ(d5dZ)edd	 	 	 d6d Z*	 	 	 	 d7d!Z+	 	 	 	 d8d"Z,d#Z-d9d$Z.d:d%Z/d;d&Z0	 d3edddedddd'	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d<d(Z1d=d)Z2eddedddd*	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d>d+Z3g d,Z4y)?u  dispatch.anu_pickup_reporter — task-2732 P0B_MINIMUM_PICKUP reporting path.

driver(scan_once)가 result.json 을 수거·verdict 한 뒤, 그 verdict 를
**회장 chat(6937032012)** 에 ``PASS`` / ``HOLD`` / ``CALLBACK_MISSING`` 한 줄로
자동 보고한다. driver 는 수거·closeout·verdict 만 수행하고(launcher_fn=None,
wake/callback launch 0), 이 모듈은 그 결과를 보고만 한다 — **깨우기/relay 0**.

설계 불변식(SPEC-ANCHOR):
  - ANCHOR-1: wake/callback launch 0. 이 모듈은 wake-launcher / cron prompt / ANU relay
    를 호출하지 않는다. 보고는 **cokacdir --sendfile** (key 불요·agent wake 0)만 사용한다.
  - ANCHOR-2: verdict → 회장 PASS/HOLD/CALLBACK_MISSING. ANU/봇 key literal 노출 0
    (sealed loader 는 보고 경로에 불필요 — sendfile 은 key 없이 동작; argv 에 key 가
    실릴 경우를 대비한 redaction helper 만 제공).
  - canonical write 0: dedupe ledger·보고 임시파일은 memory/p0b_state/ (isolated state)에만
    기록한다. canonical events(memory/events) 변형 0.

reporting 매핑 (§4):
  - ``CLOSEOUT_DONE`` / ``VERDICT_AUTHORITATIVE`` → ``PASS``
    (단, result.json callback contract 위반/부재 신호 시 → ``CALLBACK_MISSING`` 우선).
  - ``QUARANTINE`` (owner-proof FAIL: NOT_ANU/QUERY_FAILED 포함) → ``HOLD``.
  - result.json ``callback_schedule_created=true``(contract 위반) 또는 callback 부재
    신호 → ``CALLBACK_MISSING``.
  - ``NOOP_*`` (disabled/not-target/not-ready/legacy/max-files-defer) → 보고 안 함.
  - 그 외(WAKE_BUILT/PICKUP_SKIP/FIRE_FAILED 등) → 보고 안 함(spec 매핑 밖).
  - 보고 1건당 1회 — task_id dedupe(ledger marker).
    )annotationsN)	dataclass)datetime	timedeltatimezone)CallableListOptional)ANU_CHAT_IDsend_reportz/home/jay/workspacePASSHOLDCALLBACK_MISSINGCLOSEOUT_DONE
QUARANTINEAUTHORITATIVEz$memory/p0b_state/reporter_sent.jsonlz memory/p0b_state/reporter_outboxz.result.jsonz***REDACTED***)z--key	   )hoursc                  l    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dZy)ReportDecisionstrtask_idOptional[str]decisionverdict sha256FboolsentdedupedNerrorc                    | j                   | j                  | j                  | j                  | j                  | j
                  | j                  dS )Nr   r   r   r   r   r    r!   r#   )selfs    3/home/jay/workspace/dispatch/anu_pickup_reporter.pyto_jsonzReportDecision.to_jsonT   s=    ||||kkII||ZZ
 	
    )returndict)	__name__
__module____qualname____annotations__r   r   r    r!   r&    r'   r%   r   r   J   s?    LLFCD$GTE=	
r'   r   c                .   t        | t        t        f      sg S g }d}| D ]s  }|r|j                  t               d}t        |      }|j                  |       |t        v rd}Dt        D ]'  }|j                  |dz         s|dz   t        z   |d<    s u |S )u   argv list 에서 ``--key`` 다음 토큰을 마스킹한 사본을 반환(원본 불변).
    로그/evidence 에 argv 를 남길 때 key literal 노출 0 을 보장하기 위한 helper.FT=)
isinstancelisttupleappendREDACTEDr   _REDACT_FLAGS
startswith)argvoutredact_nexttoksfls         r%   redact_argvr?   a   s     dT5M*	CK JJx KH

1K $ <<S) 3h1CG Jr'   c                    t        | t              syt        | j                  dd            ryd| v rt        | j                  d            syy)u  result.json 의 callback contract 위반/부재 신호 판정.

    - ``callback_schedule_created`` truthy → True (contract 위반: executor 가
      callback 을 self-schedule. task-2731 contract 상 false 가 정상).
    - ``callback_present`` 이 명시적으로 False → True (callback 부재 신호).
    - 그 외(부재/판단 불가) → False (보수적: PASS 를 CALLBACK_MISSING 으로 오인 0).
    Fcallback_schedule_createdTcallback_present)r2   r)   r   get)result_datas    r%   _callback_missingrE   {   sG     k4(KOO7?@[(kooFX6Y1Zr'   c                    | yt        |       j                         }|sy|j                  d      ry|t        v rt	        |      rt
        S t        S |t        v rt        S y)u   driver verdict(+optional result.json) → 회장 보고 라벨 매핑.

    반환: ``PASS`` / ``HOLD`` / ``CALLBACK_MISSING`` 또는 None(보고 안 함).
    NOOP_* 및 spec 매핑 밖 verdict(WAKE_BUILT/PICKUP_SKIP/FIRE_FAILED 등)는 None.
    NNOOP_)	r   stripr8   _PASS_VERDICTSrE   REPORT_CALLBACK_MISSINGREPORT_PASS_HOLD_VERDICTSREPORT_HOLD)r   rD   vs      r%   
map_reportrO      s`     GA||GN[)**Nr'   c                    | syt         j                  j                  t        |             }|j	                  t
              r|dt        t
                S y)uM   ``.../task-2732.result.json`` → ``task-2732``. 부적합 시 빈 문자열.r   N)ospathbasenamer   endswithRESULT_SUFFIXlen)rR   bases     r%   task_id_from_pathrX      sE    77CI&D}}]#)s=))**r'   c                N    t        | xs d      j                         }|r|d d S dS )Nr      -)r   rH   )r   r=   s     r%   
_short_shar\      s,    FLb!A1Sb6Cr'   c           	     .    |  d| d| dt        |       S )uW   회장 보고 한 줄: ``task-X: PASS|HOLD|CALLBACK_MISSING · <verdict> · <sha256>``.z: u    · )r\   r   r   r   r   s       r%   build_report_liner_      s%    Yb
$witJv4F3GHHr'   c                R    |xs$ t         j                  j                  | t              S N)rQ   rR   joinREPORTER_LEDGER_RELrootledger_paths     r%   _ledger_pathrg      s    A"'',,t-@AAr'   rd   c                  t        ||      }| rt        j                  j                  |      sy	 t	        |dd      5 }|D ]Z  }|j                         }|s	 t        j                  |      }t        |t              s=|j                  d      | k(  sR ddd       y 	 ddd       y# t        t        f$ r Y xw xY w# 1 sw Y   yxY w# t        $ r Y yw xY w)uV   ledger 에 동일 task_id 보고 기록이 있으면 True(보고 1건당 1회 dedupe).Frutf-8encodingr   NT)rg   rQ   rR   isfileopenrH   jsonloads
ValueError	TypeErrorr2   r)   rC   OSError)r   re   rf   ledgerfhlineentrys          r%   already_reportedrx      s     $,F"''..0&#0 
	 B 	 zz| JJt,E eT*uyy/Cw/N
	  
	 	 
	   #I. 
	    sj   C	 B=B(-B=>B=B=C	 B=C	 (B:7B=9B::B==CC	 C	 		CCc                  t        ||      } |       j                         | |t        |xs d      d}	 t        j                  t        j
                  j                  |      xs dd       t        |dd      5 }|j                  t        j                  |d	
      dz          |j                          t        j                  |j                                ddd       y# 1 sw Y   yxY w# t        $ r}	d|	 cY d}	~	S d}	~	ww xY w)u  보고 완료 1줄 append(durable: makedirs+fsync). key literal 미기록.

    반환: 성공 시 None, 실패 시 에러 메시지(str). ★ 실패를 silently swallow 하지 않고
    호출자(report_one)에 전달하여 ReportDecision.error 로 가시화한다. dedupe ledger
    내구성 실패(디스크 IO)는 드물지만, 발생 시 다음 cycle 재전송(중복) 가능성을 명시적으로
    드러낸다. (send-first 순서: 회장 보고 누락 회피 > 드문 중복 — 누락이 더 해롭다.)r   )tsr   r   r   .Texist_okarj   rk   F)ensure_ascii
NuG   dedupe ledger 미기록(durability) — 다음 cycle 재전송 가능: )rg   	isoformatr   rQ   makedirsrR   dirnamern   writero   dumpsflushfsyncfilenors   )
r   r   r   re   rf   clockrt   recru   excs
             r%   _mark_reportedr      s     $,Fg!fl#	C_
BGGOOF+2sTB&#0 	"BHHTZZ%84?@HHJHHRYY[!	" 		"  _XY\X]^^_s=   AC- ;AC!C- !C*&C- *C- -	D6D :D Dc               <   |xs$ t         j                  j                  |t              }t	        j
                  dd|       xs d}| dt        t        j                         dz         d}t         j                  j                  ||      }	 t        j                  |d       t        |d	d
      5 }|j                  |dz          |j                          t        j                  |j                                ddd       |S # 1 sw Y   |S xY w# t        $ r Y yw xY w)u   보고 한 줄을 isolated outbox 파일로 작성하고 경로 반환(실패 시 None).
    canonical events 0 touch — memory/p0b_state/reporter_outbox 하위에만 기록.z[^A-Za-z0-9._-]_taskr{   i  z.report.txtTr|   wrj   rk   r   N)rQ   rR   rb   REPORTER_OUTBOX_RELresubinttimer   rn   r   r   r   r   rs   )	r   rv   re   
outbox_dirodirsafefnamefpathru   s	            r%   _write_report_filer      s     @d,?@D66$c73=vDfAc$))+,-.k:EGGLLu%E
D4(%w/ 	"2HHTD[!HHJHHRYY[!	" 		"  s1   %D 0AD8	D DD D 	DD   c                
   	 t        | d      5 }|j                         }ddd       t        j                  j	                  d            }t        |t              r|S dS # 1 sw Y   AxY w# t
        t        t        f$ r Y yw xY w)uY   path 를 JSON dict 로 적재. 실패/비-dict → None(listdir 없이 직접 시도용).rbNrj   )
rn   readro   rp   decoders   rq   UnicodeDecodeErrorr2   r)   )rR   ru   rawdatas       r%   _try_load_json_dictr     s{    $ 	'')C	zz#**W-. dD)43t3	 	 Z!34 s'   A+ A,A+ A($A+ +BBc               p   | si S t         j                  j                  |       }t         j                  j                  |ddd      }| t         j                  j                  ||      fD ]  }t	        |      }||c S  |dz   }	 t        j
                  |      5 }d}|D ]p  }	|t        k\  r ne|dz  }|	j                  j                  |      s/t	        t         j                  j                  ||	j                              }|e|c cddd       S  ddd       i S # 1 sw Y   i S xY w# t        $ r Y i S w xY w)u  result_path → result dict. 원위치 부재 시 processed 디렉토리에서 basename 매칭.
    읽기/파싱 실패 시 {} 반환(보수적: callback 판정 불가 → PASS 보존).

    ★ (c) MEDIUM fix: processed dir 전체를 **무조건** ``os.listdir`` 하던 scan 을 제거.
      1) 직접 후보(원위치 record_path · processed/<basename>)를 listdir 없이 먼저 시도
         — 정상 경로에서 디렉토리 scan 0.
      2) 직접 후보가 모두 부재할 때에만 충돌-회피 suffix(``.<ms>[-n]``) 변형을
         ``os.scandir`` + ``PROCESSED_SCAN_LIMIT`` 상한 bounded scan 으로 탐색
         (전체 무조건 scan·무제한 누적 폭주 회피).memory	p0b_state	processedNr{   r      )
rQ   rR   rS   rb   r   scandirPROCESSED_SCAN_LIMITnamer8   rs   )
record_pathre   rW   pdircr   prefixitscannedents
             r%   _default_result_loaderr     s1    	77K(D77<<h[AD277<<d34 "1%K
 CZFZZ 		$G $22188&&v..rww||D#((/KLD'#		$ 		$$		$ I		$ I  IsH   D( 3D5DD	D( DD( D% D( %D( (	D54D5c                   	 t        | |      }|yt        |dd      }t        |t              sy|dk(  S # t        $ r Y yw xY w)uM  allowlist-only sendfile 로 회장 chat 에 보고 파일 전송. 성공 여부 bool.

    ★ wake 0: --sendfile 만 사용(cron prompt/agent wake/relay 0). key literal 0:
    sendfile 은 key 불요. send_report 는 shell=False·고정 argv·화이트리스트 검증.

    ★ (a) HIGH fix: send 성공은 **실제 전송 성공이 확인된 경우에만** True.
      - 예외 발생 → 실패(False). 다음 cycle 재시도(ledger 미기록).
      - 결과가 None 이거나 ``returncode`` 속성/값이 없으면(계약 위반·예기치 못한 반환)
        **성공 간주 0** → 실패(False). (이전: ``getattr(res, "returncode", 0)`` 가
        None/속성부재를 0 으로 오인하여 미전송을 성공으로 기록하던 HIGH 결함을 차단.)
      - ``returncode == 0`` 이 명시적으로 확인된 경우에만 True.chat_idFN
returncoder   )r   	Exceptiongetattrr2   r   )	file_pathr   resrcs       r%   _default_senderr   9  sU    )W5 {	lD	)Bb#7N  s   5 	A A)re   senderresult_loaderrD   r   rf   r   r   c          
     x   |
xs d }
t        |      xs t        |xs d      j                         }||xs fd}	  ||      }t        |xs d      j                         s.t	        |t
              rt        |j                  d      xs d      }t        | |      }|%t        |dt        |       t        |xs d            S |s&t        d|t        |       t        |xs d      d      S t        ||	      r&t        ||t        |       t        |xs d      d
      S t        ||t        |       |      }t        |||	      }|&t        ||t        |       t        |xs d      d      S |xs fd}	 t         ||            }|s&t        ||t        |       t        |xs d      d      S t        |||||
      }t        ||t        |       t        |xs d      d
|      S # t        $ r i }Y w xY w# t        $ r3}t        ||t        |       t        |xs d      d|       cY d}~S d}~ww xY w)uC   단일 verdict 를 매핑→dedupe→보고. ReportDecision 반환.c                 4    t        j                  t              S ra   )r   nowKSTr.   r'   r%   <lambda>zreport_one.<locals>.<lambda>b  s    hll3/ r'   r   Nc                    t        |       S )N)re   )r   )pre   s    r%   r   zreport_one.<locals>.<lambda>g  s    -CAD-Q r'   r   r^   u!   task_id 미해석 — 보고 skip)r   r   r   r   r!   rd   T)r   r   r   r   r    )re   r   u   보고 파일 작성 실패c                    t        |       S )Nr   )r   )fpr   s    r%   r   zreport_one.<locals>.<lambda>  s    G D r'   u   sender 예외: u   전송 실패(returncode!=0))re   rf   r   )r   r   r   r   r   r!   )rX   r   rH   r   r2   r)   rC   rO   r   rx   r_   r   r   r   )r   result_pathr   re   r   r   rD   r   rf   r   r   r   loaderr   rv   r   sndokr   mark_errs      `   `            r%   
report_oner   S  s`    0/E,NK4E20F0L0L0NG R#Q	 -K
 v|""$K)F[__X.4"5';/Hgc'l%(2%68 	8 b8S\%(2%6>ac 	c dDg#g,%(2%6F 	F WhGfEDw4JOE}g#g,%(2%6>[] 	] 
EDCW#e* g#g,%(2%6>\^ 	^ gxd*5UDH'Hc'l!$V\r!2XO OW  	K	<  Wg#g,%(2%6PSu>UW 	WWs0    G+ :G= +G:9G:=	H9(H4.H94H9c                ^    t        | t              r| j                  |      S t        | |d      S )u>   DriverRecord(dataclass) 또는 dict 모두에서 field 추출.N)r2   r)   rC   r   )recordr   s     r%   _record_fieldr     s*    &$zz$64&&r'   re   r   r   r   rf   r   r   c                   g }| xs g D ]R  }	t        |	d      }
t        |	d      xs d}t        |	d      xs d}|j                  t        |
|||||||||
             T |S )u  scan_once 가 반환한 DriverRecord 목록을 순회하며 보고. 결정 목록 반환.

    각 record: verdict + result_path. sha256 은 record(없으면 result.json)에서 추출.
    NOOP_*/매핑 밖 → 보고 안 함. PASS/HOLD/CALLBACK_MISSING → dedupe 후 1회 전송.
    r   r   r   r   r   )r   r5   r   )recordsre   r   r   r   rf   r   r   r:   r   r   r   r   s                r%   report_recordsr     s      !#C}" Y/#C7=2sH-3

:'#!
 		  Jr'   )CANONICAL_ROOTrK   rM   rJ   VERDICT_AUTHORITATIVErc   r   r   r6   r   r   r?   rO   rX   r_   rx   r   r   )r(   z	List[str])rD   Optional[dict]r(   r   ra   )rD   r   r(   r   )rR   r   r(   r   )r   r   r(   r   )r   )
r   r   r   r   r   r   r   r   r(   r   )re   r   rf   r   r(   r   )r   r   re   r   rf   r   r(   r   )r   r   r   r   r   r   re   r   rf   r   r(   r   )
r   r   rv   r   re   r   r   r   r(   r   )rR   r   r(   r   )r   r   re   r   r(   r)   )r   r   r   r   r(   r   )r   r   r   r   r   r   re   r   r   Optional[Callable]r   r   rD   r   r   r   rf   r   r   r   r   r   r(   r   )r   r   )r   r3   re   r   r   r   r   r   r   r   rf   r   r   r   r   r   r(   zList[ReportDecision])5__doc__
__future__r   ro   rQ   r   r   dataclassesr   r   r   r   typingr   r	   r
   dispatch.anu_terminal_relayr   r   r   rK   rM   rJ   DRIVER_CLOSEOUT_DONEDRIVER_QUARANTINEr   rI   rL   rc   r   rU   r6   r7   r   r   r?   rE   rO   rX   r\   r_   rg   rx   r   r   r   r   r   r   r   r   r   __all__r.   r'   r%   <module>r      s|  4 #  	 	  ! 2 2 + + A ' ,  '   '  '(=>#% = 8  yq!" 
 
 
,4"0 
IB 3A26"/;?._ -_9F_6#05B*  4"L: CO
 !%(,"&!% $ $COCOCO CO
 CO CO &CO  CO CO CO CO CO CON' !%(,!% $ $!! ! 	!
 &! ! ! ! ! !Hr'   