
    j              
      |   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m	Z	 ddl
m
Z
mZ ddlmZmZmZmZ ddlmZmZmZmZ dZd	Zd
ZdZej6                  j9                  eddd      Zej6                  j9                  ed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Z0dZ1d Z2d!Z3d"Z4ee5ge6f   Z7dGd#Z8dHd$Z9dId%Z: G d& d'      Z;e G d( d)             Z<	 	 	 	 	 	 dJd*Z=e G d+ d,             Z>dKd-Z?d.Z@dLd/ZAdMd0ZBd1ddedd2d3	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dNd4ZCedd1d5	 	 	 	 	 	 	 	 	 dOd6ZDd7ZEe G d8 d9             ZFedd:	 	 	 	 	 	 	 	 	 dPd;ZGe G d< d=             ZHdQd>ZIeeddd1dd eJe      d2d?		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dRd@ZKdAdB	 	 	 	 	 dSdCZLdTdUdDZMg dEZNeOdFk(  r eP eM             y)VuE  dispatch.anu_owned_callback_enforcement — task-2717 ANU_OWNED_CALLBACK_
ENFORCEMENT (executor self-collector 구조 차단, executable automation).

회장 verbatim 2026-05-31: "문서/메모리 박제로 끝내지 말고 callback 생성 권한을
executor 에게 주지 않는 실행 구조로 바꾼다."

발단 (incident: memory/events/task-2716+2.self-collector-incident-260531.json):
dev4 executor 가 'ANU collector' 콜백을 자기 self-key(7943afbe…, schedule
A372DD6B) 로 등록(SELF_COLLECTOR). envelope 텍스트는 ``self_key_used=false /
collector_role=ANU / "ANU key <ANU_KEY_REDACTED> 사용"`` 이라 주장했지만 실제
schedule owner = dev4 self-key. ANU key 로 cron-history 조회 → "schedule not
found or access denied" (ANU 소유 아님). 즉 envelope 텍스트는 거짓이었다.

이 모듈은 그 재발을 **NEED 제거 + TRUST 제거** 양면 실행 구조로 막는다.

NEED 제거 (봇이 self-fire 할 이유 없앰)
--------------------------------------
* ``executor_write_result_json`` — executor 는 callback schedule 을 만들지
  않는다. **지정 result JSON 만** 작성한다(완료조건 = result JSON 작성, NOT
  callback schedule 생성). Layer A / NO-CRON: schedule register 0,
  subprocess 0.
* ``anu_runner_pickup_and_fire`` — 별도 **ANU-key runner** 가 result JSON 을
  pickup → **ANU-owned callback 발사 request(데이터 argv)** 를 생성한다.
  owner_key 가 독립 ANU key 가 아니면 fail-closed(argv=None). 실 cron 발사/
  subprocess 는 이 함수가 직접 하지 않는다 (자가발사 금지 — 본 task 가 고치는
  그 패턴; argv 만 surface, 권한 있는 ANU 세션이 실행).

TRUST 제거 (self-fire 해봤자 격리)
----------------------------------
* ``resolve_authoritative_owner`` — envelope 의 ``self_key_used`` /
  ``collector_role`` / "ANU key 사용" 문구를 **믿지 않는다**. ANU key 로
  ``cokacdir --cron-history <schedule_id>`` 를 실제 조회하여 schedule 의 실
  owner 를 authoritative 하게 판정한다. ANU key 가 조회 가능 ⇔ ANU 소유.
  조회 거부(access denied)/부재 ⇔ 비-ANU 소유.
* ``verify_collector_authoritative`` — 실 owner 가 ANU 가 아니면
  ``SELF_COLLECTOR_QUARANTINE`` 로 자동 격리 + ``NON_AUTHORITATIVE``. 격리된
  self-collector 결과는 보존하되(non-blocking, 작업물 미파기) 자동 push/merge
  판단에는 절대 사용하지 않는다(``usable_for_auto_merge=False``).
* ``write_quarantine_record`` — 격리 증거 artifact 를 durable 하게 남긴다.

정렬 (중복 신설 0)
------------------
owner-key 판정의 권위 게이트는 ``dispatch.callback_owner_enforcer.
enforce_callback_owner`` 를 **재사용**한다(신규 owner 검증 로직 미신설).
ANU-owned callback request argv 생성은 ``dispatch.normal_fallback_callback_
helper.build_anu_owned_callback_request`` 를 **재사용**한다(lazy import).
task-2713(CALLBACK_LOCATION_METADATA) 의 worktree-mismatch envelope 필드 ·
task-2715(REMEDIATION_HEAD_ADVANCE) 와 기능 중복 0 — 본 모듈은 callback
**ownership(소유권) authority** 축만 담당한다.

Layer A / NO-CRON: 이 모듈은 cron register/remove 0, dispatch 0, merge 0.
실 cokacdir 호출은 ``RealCokacdirCronHistoryProbe`` (운영 전용, 테스트는
주입 fake 만 사용) 의 read-only ``--cron-history`` 조회 1건뿐이다.
    )annotationsN)	dataclassfield)datetimetimezone)CallableListOptionalSequence)ANU_KEY_2553DEFAULT_ANU_KEYSSELF_COLLECTOR_FORBIDDEN
is_anu_keyz*dispatch.anu_owned_callback_enforcement.v1
6937032012z/usr/local/bin/cokacdirz/home/jay/workspacememoryevents
quarantinei`T  AUTHORITATIVEQUARANTINEDNON_AUTHORITATIVEPENDING_OWNER_PROOFREJECTEDANU_OWNED_AUTHORITATIVESELF_COLLECTOR_QUARANTINEOWNER_QUERY_FAILEDSTALE_ENVELOPE_REJECTEDENVELOPE_BINDING_MISMATCHSCHEDULE_ID_MISSINGNO_RESULT_JSON	ANU_OWNEDNOT_ANU_OWNED_OR_ACCESS_DENIEDQUERY_FAILED)z	not foundzaccess deniedprobe_exec_failedNOT_ACTIVATEDPHASE2_REQUIREDc                 H    t        j                  t        j                        S N)r   nowr   utc     X/home/jay/workspace/.worktrees/task-2721-dev1/dispatch/anu_owned_callback_enforcement.py_nowr-      s    <<%%r+   c                $    | j                  d      S )Nz%Y-%m-%dT%H:%M:%SZ)strftime)dts    r,   _isor1      s    ;;+,,r+   c               ~    | |S t        |       j                         }|s|S t        j                  dd|      }|xs |S )u  파일명 컴포넌트 sanitize — path separator(``/`` 와 ``\`` 양쪽)를 ``_`` 로
    치환한다. ``str.replace(os.sep, "_")`` 는 Linux 에서 ``/`` 만 치환하고 ``\``
    를 남겨 cross-platform 으로 안전하지 않으므로 양쪽을 명시 치환한다.
    ``value`` 가 None/빈/공백이면 ``str(None)='None'`` 같은 의도치 않은 파일명
    대신 ``default`` 를 반환한다(silent 'None' 파일명 0).z[\\/]+_)strstripresub)valuedefaults	sanitizeds       r,   _sanitize_filename_componentr<      sE     }E
Ay#q)Ir+   c                  4    e Zd ZdZeZeedd	 	 	 ddZddZ	y)	RealCokacdirCronHistoryProbeu   실 ``cokacdir --cron-history <sid> --chat <chat> --key <ANU_KEY>`` wrapper.

    ★ 핵심: 조회 key 는 **ANU key** 다. cron-history 는 ownership-checked API —
    ANU key 로 조회가 성공(``status=ok``)하면 그 schedule 은 ANU 소유, error(
    "not found or access denied")면 비-ANU 소유다. 즉 envelope 텍스트가 아니라
    실제 소유권을 묻는다.

    운영 collector 전용. 본 task regression 은 fake probe 를 주입하므로 이
    subprocess 경로는 테스트에서 실행되지 않는다.
    <   )anu_keychat_idtimeoutc               @    || _         t        |      | _        || _        y r'   )r@   r4   rA   rB   )selfr@   rA   rB   s       r,   __init__z%RealCokacdirCronHistoryProbe.__init__   s    7|r+   c           	        	 t        j                  | j                  dt        |      d| j                  d| j
                  gdd| j                        }t        j                  |j                  xs dj                         xs d      S # t        t        t         j                  f$ r}dt        d	| d
cY d }~S d }~ww xY w)Nz--cron-historyz--chatz--keyT)capture_outputtextrB    z{}errorzprobe exec failed: )status
error_kindmessage)
subprocessrunbinaryr4   rA   r@   rB   jsonloadsstdoutr5   OSError
ValueErrorSubprocessErrorPROBE_EXEC_FAILED_KIND)rD   schedule_idprocexcs       r,   __call__z%RealCokacdirCronHistoryProbe.__call__   s    	>>KK$$LLLL  $D ::t{{0b779ATBBZ%?%?@ 	
 "406 		s   BB B=&B82B=8B=N)r@   r4   rA   r4   rB   intreturnNone)rX   r4   r]   dict)
__name__
__module____qualname____doc__COKACDIR_BINARYrP   ANU_KEYANU_CHAT_IDrE   r[   r*   r+   r,   r>   r>      s0    	 F)0 "'+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Zded<    ee      Zded<   ddZ	y)OwnerResolutionuB   authoritative cron-history 조회로 판정한 schedule 실 owner.r4   schemarX   outcomeboolowner_is_anuquery_okOptional[int]	run_countOptional[str]
raw_statusrI   raw_messagedefault_factory	List[str]reasonsc                    | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  t        | j                        d	S )N	ri   rX   rj   rl   rm   ro   rq   rr   rv   )
ri   rX   rj   rl   rm   ro   rq   rr   listrv   rD   s    r,   to_jsonzOwnerResolution.to_json   sV    kk++|| --//++DLL)

 
	
r+   Nr]   r_   )
r`   ra   rb   rc   __annotations__rr   r   ry   rv   r{   r*   r+   r,   rh   rh      sJ    LKLNKt4GY4
r+   rh   c               V   | rt        |       j                         s)t        t        t        | xs d      t        dddddg      S t        |      s%t        t        t        |       t        dddddg      S 	  |t        |             }t        |t              s%t        t        t        |       t        dddddg      S t        |j                  d	      xs d      j                         j                         }t        |j                  d
      xs d      }|dk(  rF|j                  d      }	 |t        |      nd}t        t        t        |       t        dd||dg      S |dk(  rt        |j                  d      xs d      t         k(  r&t        t        t        |       t        ddd||dg	      S |j                         t#        fdt$        D              r&t        t        t        |       t&        ddd||dg	      S t        t        t        |       t        ddd||d|g	      S t        t        t        |       t        ddd|xs d|d|dg	      S # t        $ r2}t        t        t        |       t        ddddd| g      cY d}~S d}~ww xY w# t        t        f$ r d}Y lw xY w)u  schedule 의 실 owner 가 ANU 인지 cron-history(ANU key 조회)로 판정.

    * ``status == "ok"``  → ANU key 가 schedule 조회 성공 ⇒ **ANU 소유**.
    * ``status == "error"`` 이고 message 가 "not found"/"access denied" 포함
      → ANU key 로 조회 거부/부재 ⇒ **비-ANU 소유** (regression 2 의 access
      denied 판정 포함).
    * 그 외(파싱 불가 / subprocess 실패 / 알 수 없는 응답) → **QUERY_FAILED**
      (fail-closed; envelope 텍스트로 fallback 하지 않는다).

    envelope 의 owner_key/self_key_used/collector_role 텍스트는 이 함수에
    **입력되지 않는다** — 오직 실제 schedule 소유권만 본다.
    rI   FNu9   schedule_id 없음 — authoritative owner 조회 불가.)ri   rX   rj   rl   rm   ro   rq   rv   uT   probe 가 실행 가능(callable)하지 않습니다 — 설정 오류(fail-closed).ud   cron-history probe 예외 → owner proof 미확정(PENDING, 재시도 가능, fail-closed 아님): ua   cron-history 응답이 dict 아님(garbled) → owner proof 미확정(PENDING, 재시도 가능).rK   rM   okcountTuS   ANU key 로 cron-history 조회 성공 — schedule 은 ANU 소유 (authoritative).rJ   rL   u  cron-history probe 실행 실패(인프라 장애 — 바이너리 부재/타임아웃/파싱 등) → owner proof 미확정(PENDING, 재시도 가능, fail-closed 아님). 실 access-denied 응답이 아니므로 OWNER_NOT_ANU(false quarantine) 로 분류하지 않는다.rx   c              3  &   K   | ]  }|v  
 y wr'   r*   ).0toklows     r,   	<genexpr>z.resolve_authoritative_owner.<locals>.<genexpr>P  s     ;cscz;s   u   ANU key 로 cron-history 조회 거부/부재 (not found / access denied) — schedule 은 비-ANU 소유 (self-collector 신호).uD   cron-history error 가 access-denied 신호 아님 → fail-closed: u#   알 수 없는 cron-history status=u6    → owner proof 미확정(PENDING, 재시도 가능).)r4   r5   rh   SCHEMAr   callable	ExceptionOWNER_PENDING
isinstancer_   getlowerr\   	TypeErrorrU   	OWNER_ANUrW   any_ACCESS_DENIED_TOKENSOWNER_NOT_ANU)	rX   proberawrZ   rK   rM   r   count_ir   s	           @r,   resolve_authoritative_ownerr      s   " c+.446s;+<"'=&UUtPQ	
 	
 E? s;'7&UUtkl	
 	

C$% c4 s;'7!t/	
 	
 "(b)//1779F#'')$*+G~ 	$)$5c%j4G s;'7D4&#	
 	
 
 sww|$*+/EE"3{+;%EE6wR	
 
 mmo;%:;;"3{+;%ED6w		 	 s;'7&UUv7V+	
 	
 3{#3EE6>Tw1& <+ +
	 e  	
s;'7!t003u6	
 	
	
: :& 	G	s0   7I ,J 	J'JJJJ('J(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Zded<    ee      Zded<   e	dd       Z
e	dd       ZddZy)CollectorVerificationu   collector 권위 판정 결과. self-collector 결과는 NON_AUTHORITATIVE 이며
    자동 push/merge 판단에 절대 사용하지 않는다(``usable_for_auto_merge``).r4   ri   verdictclassificationtask_idrX   Optional[dict]owner_resolutionr_   envelope_claimsrk   work_preservedNrp   quarantine_record_pathrs   ru   rv   c                (    | j                   t        k(  S r'   r   VERDICT_AUTHORITATIVErz   s    r,   r   zCollectorVerification.ok  s    ||444r+   c                (    | j                   t        k(  S )u   오직 authoritative(실 owner=ANU) 일 때만 자동 push/merge 판단 사용 가능.
        quarantine / non-authoritative / rejected → 절대 0 (회장 doctrine).r   rz   s    r,   usable_for_auto_mergez+CollectorVerification.usable_for_auto_merge  s     ||444r+   c                   | j                   | j                  | j                  | j                  | j                  | j
                  t        | j                        | j                  | j                  | j                  t        | j                        dS )N)ri   r   r   r   rX   r   r   r   r   r   rv   )ri   r   r   r   rX   r   r_   r   r   r   r   ry   rv   rz   s    r,   r{   zCollectorVerification.to_json  sq    kk||"11||++ $ 5 5#D$8$89"11%)%?%?&*&A&ADLL)
 	
r+   r]   rk   r|   )r`   ra   rb   rc   r}   r   r   ry   rv   propertyr   r   r{   r*   r+   r,   r   r   r  s|    ] KLL$$,0M0t4GY45 5 5 5

r+   r   c                    | j                  d      xs | j                  d      | j                  d      | j                  d      | j                  d      dS )uY   envelope 가 주장하는(=불신해야 하는) 소유권 텍스트만 추출(기록용).	owner_keycollector_keyself_key_usedcollector_rolerX   )claimed_owner_keyclaimed_self_key_usedclaimed_collector_roleclaimed_schedule_id)r   )envelopes    r,   _extract_claimsr     sP     &\\+6 )<<(!)o!>"*,,/?"@'||M: r+   l    J)c                R   | yt        | t        t        f      r?t        | t              s/	 t	        j
                  t        |       t        j                        S t        |       j                         }|sy	 t	        j
                  t        |      t        j                        S # t        t        t        t        f$ r Y yw xY w# t        t        t        t        f$ r Y nw xY w	 |}|j                  d      r|dd dz   }t	        j                  |      }|j                    |j#                  t        j                        }|S # t        t        f$ r Y nw xY wdD ]M  }	 t	        j$                  ||      j#                  t        j                        c S # t        t        f$ r Y Kw xY w y)u*  단일 timestamp 값 → tz-aware UTC datetime.

    숫자형 epoch(int/float 및 숫자 문자열)와 ISO/날짜 문자열 포맷을 모두 처리한다.
    파싱 불가 시 None (TypeError/ValueError/OverflowError/OSError 모두 핸들 —
    숫자 epoch 가 ``strptime`` 에 들어가 TypeError 로 터지던 문제 포함).

    수정2 (task-2720 2차 fix, High #466):
      * "Z" suffix ISO(예: "2026-06-01T04:00:00Z") — 회귀 0 으로 계속 파싱.
      * timezone offset ISO(예: "2026-06-01T13:00:00+09:00") — 신규 지원.
      * datetime.fromisoformat 사용(Python 3.7+). "Z" 는 fromisoformat 이
        직접 처리 못하므로 끝의 "Z" 를 "+00:00" 으로 치환 후 파싱.
      * naive datetime 은 UTC 로 간주하여 tzinfo=timezone.utc 부여(tz-aware 정규화).
    N)tzZz+00:00)tzinfo)z%Y-%m-%d %H:%M:%Sz%Y-%m-%d)r   r\   floatrk   r   fromtimestampr   r)   r   rU   OverflowErrorrT   r4   r5   endswithfromisoformatr   replacestrptime)valr:   iso_sr0   fmts        r,   _parse_timestamp_valuer     s{     {#U|$ZT-B	))%*FF 	CA%%eAh8<<@@ :}g> 		 z=': 
>>##2J)E##E*998<<0B	z"  	$$Q,44HLL4II:& 		 sH   -B$ 6-C $C ?C CC#AE EE3FF$#F$c                   d}dD ]c  }| j                  |      }| t        |t              r|j                         s7d}t	        |      }|Gt        ||z
  j                               c S  |rt        S y)u  envelope 발급 시각 → now 까지 경과 초.

    * timestamp 키가 아예 없으면 None (계산 불가 — 호출부가 staleness 미적용).
    * 값이 하나라도 파싱되면 해당 경과 초를 반환(숫자 epoch 포함).
    * 값은 존재하나 전부 파싱 실패 → ``_UNPARSEABLE_TS_STALE_SECONDS`` 반환
      (항상 stale 로 reject). 파싱 실패를 None 으로 삼켜 staleness 를 silent
      bypass 하지 않는다(silent pass 0).
    F)recorded_at
created_attsts_utcfired_atNT)r   r   r4   r5   r   r\   total_seconds_UNPARSEABLE_TS_STALE_SECONDS)r   r(   	saw_valuekeyr   r0   s         r,   _envelope_age_secondsr     sy     IH 3ll3;:c3/			#C(>b//1223 ,,r+   rI   T)executor_keyrX   r(   max_envelope_age_secondsquarantine_dirwrite_quarantinec        	           |xs
 t               }t        |t              s*t        t        t
        t        t        |       ddi ddg	      S t        |      }	|xs t        }
t        |j                  d      xs d      j                         }|t        |       j                         k7  r1t        t        t
        t        t        |       dd|	dd|d| d	g	      S t        ||
      }|V||kD  rQt        t        t
        t        t        |       t        |xs |j                  d      xs d      d|	dd| d| dg	      S t        |xs |j                  d      xs d      j                         }|s*t        t        t        t        t        |       dd|	ddg	      S t!        ||      }|j"                  rNt        t        t$        t&        t        |       ||j)                         |	ddgt+        |j,                        z   	      S |j.                  t0        k(  rnt        t        t2        t4        t        |       ||j)                         |	ddd|dgt+        |j,                        z   	      }|r	 t7        ||
||      |_        |S |S |j.                  t>        k(  rNt        t        t@        tB        t        |       ||j)                         |	ddgt+        |j,                        z   	      S t        t        t        tD        t        |       ||j)                         |	ddgt+        |j,                        z   	      S # t:        $ r)}|j,                  j=                  d|        Y d}~|S d}~ww xY w)u  collector 의 권위 판정 (TRUST 제거 핵심 진입점).

    회장 doctrine 적용 순서 (fail-closed):
      1. envelope ↔ task_id binding (stale receipt 재사용 차단, regression 3).
      2. envelope 발급 시각이 너무 오래됨 → ``STALE_ENVELOPE_REJECTED``
         (regression 3).
      3. schedule_id 확보(인자 우선, 없으면 envelope) — 없으면 격리 불가 판정.
      4. ``resolve_authoritative_owner`` 로 **실 owner** 판정 (envelope 텍스트
         미사용).
         * 실 owner=ANU → ``AUTHORITATIVE`` (regression 4).
         * 실 owner≠ANU (access denied/not found) → ``SELF_COLLECTOR_
           QUARANTINE`` + ``NON_AUTHORITATIVE`` (regression 1·2·6). 작업물은
           보존(non-blocking), 자동 push/merge 사용 0.
         * 조회 실패 → ``NON_AUTHORITATIVE`` / ``OWNER_QUERY_FAILED``
           (fail-closed).

    envelope.owner_key / self_key_used / collector_role 는 ``envelope_claims``
    로 기록만 하고 판정에는 절대 쓰지 않는다.
    rI   NTuM   envelope 이 올바른 dict 형식이 아닙니다 — 판정 불가(reject).)	ri   r   r   r   rX   r   r   r   rv   r   uX   envelope.task_id != caller task_id — stale/foreign receipt 재사용 reject: envelope=z caller=.)r(   rX   u   envelope 발급 후 u   s 경과 > u<   s 임계 — stale envelope 재사용 reject (regression 3).ul   schedule_id 없음 — authoritative owner 조회 불가 → NON_AUTHORITATIVE (자동 push/merge 사용 0).)r   u   schedule 실 owner = ANU (cron-history authoritative) — AUTHORITATIVE callback, 자동 push/merge 판단 사용 가능 (regression 4).u  schedule 실 owner ≠ ANU (envelope 텍스트가 ANU 주장이어도 cron-history 권위가 우선) — SELF_COLLECTOR_QUARANTINE 자동 격리, NON_AUTHORITATIVE. 작업물은 보존(non-blocking) 하되 자동 push/merge 판단 사용 0 (regression 1·2·6).zexecutor_key=uv    가 self-key 로 collector 를 흉내냈을 가능성 — owner 주장은 무시되고 실 소유권으로 격리됨.r   r(   r   u,   quarantine 기록 실패(판정은 유지): u   owner proof 미확정(cron-history 결정적 답 없음) → PENDING_OWNER_PROOF (재시도 가능). fail-closed 와 구분 — 자동 push/merge 사용 0(미확정), 작업물 보존.u|   authoritative owner 조회 fail-closed → NON_AUTHORITATIVE (envelope 텍스트로 fallback 0, 자동 push/merge 사용 0).)#r-   r   r_   r   r   VERDICT_REJECTEDCLS_ENVELOPE_BINDING_MISMATCHr4   r   DEFAULT_QUARANTINE_DIRr   r5   r   CLS_STALE_ENVELOPE_REJECTEDVERDICT_NON_AUTHORITATIVECLS_SCHEDULE_ID_MISSINGr   rl   r   CLS_ANU_OWNED_AUTHORITATIVEr{   ry   rv   rj   r   VERDICT_QUARANTINEDCLS_SELF_COLLECTOR_QUARANTINEwrite_quarantine_recordr   rT   appendr   VERDICT_PENDING_OWNER_PROOFCLS_PENDING_OWNER_PROOFCLS_OWNER_QUERY_FAILED)r   r   r   r   rX   r(   r   r   r   claimsqdirenv_task_idagesidresverificationrZ   s                    r,   verify_collector_authoritativer     s   > -Ch% %#38Lb!2de
 	
 X&F33D hll9-34::<Kc'l((**$#38Lb!6..9_HWKqR

 
	
  c
2C
3!99$#36LKL8<<+FL"M!6&se;7O6P QM M
 	
 k>X\\-8>B
?
E
E
GC$#<2Lb!6B

 
	
 &c
7C
$#86Lc [[]F" S[[!	"
 	
 {{m#,#68Lc [[]FJ  / 0i i S[[!"
 6M 3!-73 |
{{m# %#>2Lc [[]FK S[[!	"
 	
 !8-G#N
 
 
1  $$++B3%H  	s   (L/ /	M!8MM!r   c               B   |xs
 t               }t        j                  |d       t        | j                  d      }t        | j
                  d      }t        j                  j                  || d| d      }dd	d
t        |      | j                  | j
                  |dd| j                         dd}| dt        j                          d}t        |dd      5 }	t        j                  ||	dd       ddd       t        j                  ||       |S # 1 sw Y   !xY w)uI  self-collector 격리 증거 artifact 를 durable 하게 기록.

    회장 doctrine: self-collector 결과 삭제 금지 — NON_AUTHORITATIVE 로 보존 후
    독립 ANU 재검증으로 대체. 본 함수는 그 보존 증거를 남긴다(작업물 파기 0).
    파일명: ``<task_id>.<schedule_id>.quarantine.json``.
    Texist_okunknownr9   nosidr   z.quarantine.jsonz5dispatch.anu_owned_callback_enforcement.quarantine.v1r   u/   SELF_COLLECTOR_QUARANTINE — NON_AUTHORITATIVEFu   self-collector 결과 보존(삭제 금지). 독립 ANU 재검증으로 대체. self-collector 의 검증 주장 신뢰 금지.)ri   record_typer   r   r   rX   r   r   r   r   disposition.tmpwutf-8encoding   ensure_asciiindentN)r-   osmakedirsr<   r   rX   pathjoinr1   r{   getpidopenrQ   dumpr   )
r   r   r(   r   	safe_tasksafe_sidr   recordtmpfhs
             r,   r   r     s    -CKK.,iI ,  'H 77<<9+Qxj@P(QRDI2KCy''#//$!&$,,.>F  F!BIIK=
%C	c3	) <R		&"5;<JJsDK< <s   DDRESULT_JSON_WRITTENc                      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	<    ee
      Zded<   edd       Z	ddZ
y)ExecutorResultWriteuD   executor 완료 = result JSON 작성 (NOT callback schedule 생성).r4   ri   completion_signalr   result_json_pathrk   schedule_createdcallback_firedrs   ru   rv   c                d    | j                   t        k(  xr | j                   xr | j                   S r'   )r  EXECUTOR_COMPLETION_SIGNALr  r  rz   s    r,   r   zExecutorResultWrite.ok  s:     ""&@@ ()))('''	
r+   c           	         | j                   | j                  | j                  | j                  | j                  | j
                  t        | j                        dS )Nri   r  r   r  r  r  rv   )ri   r  r   r  r  r  ry   rv   rz   s    r,   r{   zExecutorResultWrite.to_json  sJ    kk!%!7!7|| $ 5 5 $ 5 5"11DLL)
 	
r+   Nr   r|   )r`   ra   rb   rc   r}   r   ry   rv   r   r   r{   r*   r+   r,   r
  r
    sM    NKLt4GY4
 
	
r+   r
  )
result_dirr(   c           	     Z   |xs
 t               }t        j                  |d       t        | d      }t        j                  j                  || d      }t        |      }|j                  dt        |              t        |d<   t        |      |d<   d	|d
<   d	|d<   | dt        j                          d}t        |dd      5 }t        j                  ||d	d       ddd       t        j                  ||       t!        t"        t        t        |       |d	d	dg      S # 1 sw Y   CxY w)u  executor 완료조건 = result JSON 작성 (NEED 제거).

    executor 는 callback schedule 을 만들지 않고 callback 을 쏘지도 않는다.
    오직 지정 result JSON 만 작성한다. 별도 ANU-key runner 가 이 파일을 pickup
    하여 ANU-owned callback 을 발사한다. (Layer A / NO-CRON — schedule register
    0, subprocess 0, callback fire 0.)

    payload 에 schedule_id/owner_key/collector_role 같은 소유권 주장이 들어와도
    executor 는 schedule 을 만들 권한이 없으므로 무의미하다 — 작성하는 것은
    결과 데이터(report_path/sha256/summary/expected_files 등)뿐이다.
    Tr   r   r   z.result.jsonr   r  
written_atFschedule_created_by_executorcallback_fired_by_executorr   r   r   r   r   r   r   Nu   executor 완료 = result JSON 작성 only. callback schedule 생성 0, callback 발사 0 (NEED 제거). ANU runner 가 pickup 하여 발사.r  )r-   r   r   r<   r   r   r_   
setdefaultr4   r  r1   r   r  rQ   r  r   r
  r   )	r   payloadr  r(   r  r   r  r  r  s	            r,   executor_write_result_jsonr    s   $ -CKK
T*,WiHI77<<
yk$>?D']F
iW."<F9F<-2F)*+0F'(F!BIIK=
%C	c3	) <R		&"5;<JJsD4GT
 	< <s   D!!D*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<    ee      Zded<   dZded<   e	Z
ded<   dZded<   edd       ZddZy)RunnerResultu   ANU-key runner 가 result JSON 을 pickup 하여 생성한 ANU-owned callback
    발사 request. argv 는 데이터일 뿐 — 실 발사는 권한 있는 ANU 세션이 한다
    (이 runner 가 직접 self-fire 하지 않는다).r4   ri   r   r   r  r   rk   	pickup_okOptional[List[str]]argvr   requestrs   ru   rv   rI   r   	fire_pathTdry_runc                     | j                   dk(  S )NPASS)r   rz   s    r,   r   zRunnerResult.okB  s    ||v%%r+   c                P   | j                   | j                  | j                  | j                  | j                  | j
                  | j                  t        | j                        nd | j                  | j                  | j                  | j                  t        | j                        dS )N)ri   r   r   r  r   r  r  r   r   r!  r"  rv   )ri   r   r   r  r   r  r  ry   r   r   r!  r"  rv   rz   s    r,   r{   zRunnerResult.to_jsonF  sx    kk|||| $ 5 5'+yy'<DO$||"11||DLL)
 	
r+   Nr   r|   )r`   ra   rb   rc   r}   r   ry   rv   r   FIRE_NOT_ACTIVATEDr!  r"  r   r   r{   r*   r+   r,   r  r  .  sx    ; KLLNO
t4GY4NC'Is'GT& &
r+   r  c          	         d| j                  dd       d| j                  dd       d| j                  dd       d| j                  d	d       d
d| d| dt         g}dj                  |      S )uH   result JSON → envelope-only callback prompt (자유 지시문 금지).ztask_id=r   rI   zresult_path=r  zreport_path=report_pathzsha256=sha256zcollector_role=ANUz
owner_key=zchat_id=zcanonical_root=
)r   CANONICAL_ROOTr   )resultr@   rA   liness       r,   _build_envelope_promptr.  W  s     6::i,-.
vzz"4b9:;
vzz-456
&**Xr*+,
WI
7)
.)*	E 99Ur+   )	r@   rA   promptatdispatch_cron_idnormal_collector_cron_idfallback_callback_cron_idanu_keysno_fallbackc                   | rt         j                  j                  |       s.t        t        ddt        | xs d      |dddt        d| g
      S 	 t        | dd	      5 }t        j                  |      }ddd       t        t              s"t        t        ddt        |       |ddddg	      S |j                  d|        t        |j                  d      xs d      j!                         }|s"t        t        ddt        |       |ddddg	      S t#        ||	      r||k(  r-t        t        d|t        |       |dddd|dt$         dg	      S ddlm} |xs t+        ||t        |            } |d|||t        |      ||xs d|xs | d|xs | d||	|
      }|j,                  sFt        t        d|t        |       |dd|j/                         dgt1        |j2                        z   	      S t        t        d|t        |       |d|j4                  |j/                         dg	      S # 1 sw Y   xY w# t        t        f$ r/}t        t        ddt        |       |dddd
| g	      cY d}~S d}~ww xY w)u  ANU-key runner: result JSON pickup → **ANU-owned** callback 발사 request.

    fail-closed 핀:
      * ``anu_key`` 가 독립 ANU key 가 아니거나 executor self key 와 같으면
        REFUSE (argv=None) — runner 는 비-ANU key 로 발사하지 않는다.
      * owner-key 권위 게이트는 ``build_anu_owned_callback_request`` (=
        ``enforce_callback_owner`` 재사용)를 경유 — 중복 신설 0.

    이 함수는 실 cron 을 **직접 발사하지 않는다**(argv 데이터만 surface). 실
    발사는 권한 있는 ANU 세션이 ANU key 로 수행한다 — self-key 자가발사 금지
    (본 task 가 고치는 패턴).
    FAILrI   FNuJ   result JSON 부재(executor 미작성) — NO_RESULT_JSON, pickup 불가: )
ri   r   r   r  r   r  r  r   r   rv   rr   r   u   result JSON 파싱 실패: )	ri   r   r   r  r   r  r  r   rv   u   result JSON 이 object 아님.r  r   Tu[   result JSON 에 유효한 task_id 없음 — 잘못된 callback 발사 차단(fail-closed).zrunner owner_key=uK    가 독립 ANU key 아님 또는 executor self key 와 동일 → REFUSE (uB   ). runner 는 비-ANU key 로 callback 을 발사하지 않는다.r   ) build_anu_owned_callback_requestr@   rA   normal30sz
::dispatchz::normal)kindr   r   r   rA   r/  r0  r1  r2  r3  r4  r5  uG   owner enforcement 미통과 → ANU-owned argv 미생성 (fail-closed).r$  u   result JSON pickup 정상 → owner=독립 ANU key 게이트 PASS → ANU-owned callback 발사 argv(데이터) 생성. 실 발사는 권한 있는 ANU 세션이 수행(자가발사 0) (regression 5).)r   r   isfiler  r   r4   CLS_NO_RESULT_JSONr  rQ   loadrT   rU   r   r_   r  r   r5   r   r   (dispatch.normal_fallback_callback_helperr9  r.  r   r{   ry   rv   r  )r  r   r@   rA   r/  r0  r1  r2  r3  r4  r5  r  r,  rZ   r   r9  callback_promptreqs                     r,   anu_runner_pickup_and_firerD  f  s   8 277>>2B#C62 !1!7R8G$-+.0	
 		
	
"C': 	#bYYr]F	# fd#62 !12g$56	
 	
 (*:;&**Y'-2.446G 62 !12gt'	
 	
 gx(W-D67 !12gt#G; /<<T;U VRR		
 		
   6W!O +!G;)Cy
-C$<7)8(<";C" 6667 !12gs{{}YS[[!"	
 	
 vw-.'SXXs{{}C
		 	a	# 	#Z  
62 !12g$23%89	
 	

s6   H8 H+5H8 +H50H8 8I6$I1+I61I6F)activatec          	         | j                   rt        | j                         nd}|rt        nt        }t        |dd|| j
                  | j                  dgdS )u  Phase 1 fire-path gate.

    회장 doctrine(task-2717+1): Phase 1 에서 cokacdir cron 을 정상 fire/closeout
    실행 경로로 전제하지 않는다. 본 함수는 ANU-owned argv(데이터)를 **발사하지
    않고** 차단한다:

      * ``activate=False`` (기본) → ``NOT_ACTIVATED`` — argv dry-run 까지만.
      * ``activate=True``         → ``PHASE2_REQUIRED`` — 실 fire/closeout 은
        Phase 2 에서만. Phase 1 은 절대 실 cron 을 쏘지 않는다.

    ``fired`` 는 어느 경우에도 ``False`` 다(자가발사 0 — 본 task 가 고치는 패턴).
    실 발사는 권한 있는 ANU 세션이 Phase 2 경로로 수행한다.
    NFTu   Phase 1: cokacdir cron 정상 fire/closeout 경로 비활성 — argv dry-run/build 까지만 허용. 실 fire/closeout 은 PHASE2_REQUIRED. 자가발사 0(self-collector 패턴 차단).)ri   rK   firedr"  r  r   r   rv   )r  ry   FIRE_PHASE2_REQUIREDr&  r   r   r   )runner_resultrE  r  rK   s       r,   fire_callback_requestrJ    s^    $ (5'9'94""#tD%-!3EF ((",,<
 r+   c           	        dd l }|j                  d      }|j                  dd      }|j                  d      }|j	                  dd	       |j	                  d
t
               |j	                  dd       |j	                  dd       |j	                  dd       |j                  d      }|j	                  dd	       |j	                  dd	       |j	                  dt               |j	                  dt               |j	                  dd        |j                  d      }|j	                  dd	       |j	                  dd	       |j	                  dd        |j	                  dd       |j	                  dt               |j	                  dt               |j	                  dt               |j	                  dd       |j                  |       }|j                  dk(  rt        |j                  |j                  |j                  |j                  d|j                         }t#        t%        j&                  |j)                         d              |j*                  rdS d!S |j                  dk(  rt-        |j.                  |j0                  |j2                  |j4                  |j6                  "      }t#        t%        j&                  |j)                         d              |j*                  rdS d!S |j                  dk(  r	 t9        |j:                  d#d$%      5 }	t%        j<                  |	      }
d d d        tE        |j2                  |j4                  (      }tG        |j                  tI        
tJ              r|
ni ||j0                  |jL                  |jN                  |jP                   )      }t#        t%        j&                  |j)                         d              |j*                  rdS d!S y!# 1 sw Y   xY w# t>        t@        f$ r4}t#        t%        j&                  tB        d&| d'd              Y d }~y!d }~ww xY w)*Nr   z'dispatch.anu_owned_callback_enforcement)progcmdT)destrequiredzexecutor-write-resultz	--task-id)rO  z--result-dirr   z--report-pathrI   z--sha256z	--summaryzanu-runz--result-json-pathz--executor-keyz	--anu-keyz	--chat-idz--atzcollector-verifyz--envelope-pathz--schedule-idz--quarantine-dirz--no-write-quarantine
store_true)action)r(  r)  summary)r   r  r  F)r   r   )r  r   r@   rA   r0  r8  r   r   zenvelope load failed: )r   rJ   r:  )r   r   r   r   rX   r   r   ))argparseArgumentParseradd_subparsers
add_parseradd_argumentDEFAULT_RESULT_DIRre   rf   r   
parse_argsrM  r  r   r(  r)  rR  r  printrQ   dumpsr{   r   rD  r  r   r@   rA   r0  r  envelope_pathr@  rT   rU   r   r>   r   r   r_   rX   r   no_write_quarantine)r  rS  apr7   ewrncvar   r  r   rZ   r   s                r,   mainrc    s   		 	 6 
! 
B 



6C 
/	0BOOK$O/OON,>O?OOORO0OOJO+OOKO, 
		"BOO(4O8OO$tO4OOKO1OOKO5OOFDO) 
*	+BOOK$O/OO%O5OOOTO2OO$bO1OOKO1OOKO5OO&0FOGOO+LOA
dAuu''(II }}((99
 ||
 	djjU;<FFq!!uu	(//IIIItt
 	djjU;<FFq!!uu""	aoosW= )99R=) -IIqyy
 -II!+Hd!;X++!"!6!66
 	djjU;<FFq!!/) )$ 	$**523%8:"$ % 	s0   P $O9:P 9P>P Q*QQ)(r   re   rf   r   rX   DEFAULT_MAX_ENVELOPE_AGE_SECONDSr   r   r   r   r   r   r   r   r   r   r   r?  r   r   r   r   r   rW   r&  rH  rJ  CronHistoryProber>   rh   r   r   r   r   r  r
  r  r  rD  rc  __main__)r]   r   )r0   r   r]   r4   )r8   objectr9   r4   r]   r4   )rX   r4   r   re  r]   rh   )r   r_   r]   r_   )r   rg  r]   Optional[datetime])r   r_   r(   r   r]   rn   )r   r4   r   r_   r   re  r   r4   rX   rp   r(   rh  r   r\   r   rp   r   rk   r]   r   )
r   r   r   r4   r(   rh  r   r4   r]   r4   )
r   r4   r  r_   r  r4   r(   rh  r]   r
  )r,  r_   r@   r4   rA   r4   r]   r4   )r  r4   r   r4   r@   r4   rA   r4   r/  rp   r0  rp   r1  r4   r2  rp   r3  rp   r4  zSequence[str]r5  rk   r]   r  )rI  r  rE  rk   r]   r_   r'   )r  r  r]   r\   )Qrc   
__future__r   rQ   r   r6   rN   dataclassesr   r   r   r   typingr   r	   r
   r    dispatch.callback_owner_enforcerr   re   r   r   r   r   rf   rd   r+  r   r   r   rX  rd  r   r   r   r   r   r   r   r   r   r   r   r?  r   r   r   r   r   r   rW   r&  rH  r4   r_   re  r-   r1   r<   r>   rh   r   r   r   r   r   r   r   r   r  r
  r  r  r.  tuplerD  rJ  rc  __all__r`   
SystemExitr*   r+   r,   <module>rp     s  5l #  	 	  ( ' 5 5  
6 +&Hh  WW\\.(HE  $,   ( # / 3   8  ; - 7  ; / 
 & /  	0# % 7  - 
 % (  SE4K( &- $- -` 
 
 
6}} } 	}@ &
 &
 &
R !) 6r: !%"$D$(!gg g 	g
 g g 
g "g "g g gZ 1"*'* * 
	*
 * 	*` 3  
 
 
F )"// / 	/
 
/ /d %
 %
 %
P&  .2/3#$45AA A 	A
 A A 	A A ,A  -A A A AT !! ! 
	!NVr)X z
TV
 r+   