
    0bj]                       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m	Z	 ddl
mZ ddlmZmZmZmZmZ dZdZd	Zd
ZdZdZdZeeeefZ G d de      Z G d de      Zd$dZ ed       G d d             Zed	 	 	 	 	 	 	 	 	 	 	 d%dZ G d de       Z!dZ"de"z   Z#d&dZ$ee%e%ge&f   Z' G d d      Z(d'd(d Z)d)d!Z*d'd*d"Z+e,d#k(  r e- e+             y)+u  anu_v3.dispatch_callback_contract — dispatch callback/progress-trigger 계약
(task-2614 §7b · 회장 2026-05-20 야간 필수 보강).

회장 verbatim mandate (BATCH_LEVEL_HOLD 시스템 필수 보강 — 새 범위 아님):

  모든 executor dispatch 는 (1) normal completion callback(독립 ANU key) 와
  (2) ANU-key fallback safety-net(미수신 recovery 전용·NON_BLOCKING) 둘 다
  등록하는 계약을 가져야 한다. normal callback durable-success 즉시 fallback
  은 cancel-on-success 로 제거된다(노이즈 0). fallback 은 *recovery 전용* —
  진행 트리거가 아니다(fixed-time/dead-man 진행트리거 금지). normal·fallback
  둘 다 부재면 ``DISPATCH_CONTRACT_VIOLATION`` 이다. ``result exists + normal
  missing + fallback missing`` 조건(=DISPATCH_CONTRACT_VIOLATION)에서만
  recovery watcher 가 **idempotent** 하게 독립 ANU collector 를 1회 spawn 한다
  (중복 0). 무조율 dead-man/fixed-time 진행트리거는 절대 금지 — 640665C8
  안티패턴(무조율 fixed-time ANU-key cron 진행트리거) 재발 차단. executor
  self-key 는 collector/adjudication/dispatch 에 절대 사용 불가(독립 ANU key
  전용 · +49 정본).

데몬 안전원칙 (§7b.7): sense / act / think 분리 · dry-run 우선 · 관측 후
행동. recovery watcher 는 상태를 변경하지 않고 조건만 탐지(sense)하고,
조건 충족 시 정확히 1회 spawn(act)하며, self-record 로 재진입을 차단한다.

Layer A / NO-CRON / NO-WRITE: 본 모듈은 ZERO cron register/remove, ZERO
subprocess, ZERO cokacdir, ZERO network, ZERO 파일쓰기(엔트리포인트 CLI 의
``--output`` 명시 경로 제외)다. spawn 은 *주입된* collback(``spawn_fn``)으로
위임 — 본 모듈은 실제 dispatch/cron/credential 표면을 import 하지 않는다.
기존 ``anu_v3.executor_callback_contract``(+32 doctrine) 는 무수정 — 본
모듈은 그 doctrine 을 dispatch 계약 *런타임 분류기*로 확장(중복 구현 0).
    )annotationsN)asdict	dataclassfield)Path)CallableDictListOptionalSequencez$anu_v3.dispatch_callback_contract.v1c119085addb0f8b71e41a2324a3ccdd0CONTRACT_OKDISPATCH_CONTRACT_VIOLATIONRECOVERY_SPAWNEDFALLBACK_RECOVERYc                      e Zd ZdZy)ContractViolationu0   dispatch callback 계약 위반 — fail-closed.N__name__
__module____qualname____doc__     R/home/jay/workspace/.worktrees/task-2696-dev7/anu_v3/dispatch_callback_contract.pyr   r   >   s    :r   r   c                      e Zd ZdZy)ExecutorSelfKeyForbiddenu   executor self-key 를 collector/adjudication/dispatch 에 사용 시도.

    독립 ANU key(c119085addb0f8b7) 전용 — executor self-key
    (1e41a2324a3ccdd0) 는 절대 금지(+49 정본). fail-closed.
    Nr   r   r   r   r   r   B       r   r   c                r    | t         k(  rt        d| d      | t        k7  rt        d| dt         d      y)u   collector/adjudication/dispatch key 가 독립 ANU key 인지 강제.

    executor self-key 또는 그 외 임의 key 면 ``ExecutorSelfKeyForbidden``
    (fail-closed). 약화 우회 불가.
    zexecutor self-key uc    는 collector/adjudication/dispatch 에 절대 사용 불가 — 독립 ANU key 전용(+49 정본)zcollector key u    는 독립 ANU key(u6   ) 가 아님 — fail-closed(self-chain 약화 금지)N)EXECUTOR_SELF_KEY_FORBIDDENr   INDEPENDENT_ANU_KEY)keys    r   'assert_collector_key_is_independent_anur$   J   se     ))&  (I I
 	
 !!&SG#78K7L M? ?
 	
 "r   T)frozenc                      e Zd ZU dZded<   ded<   ded<   ded<   ded<   ded	<   ded
<   ded<   ded<   ded<   ded<   ded<    ee      Zded<   ddZdddZ	y)DispatchContractRecordu5   dispatch_callback_contract.v1 레코드 (schema 1:1).strschematask_idboolnormal_callback_presentfallback_presentresult_presentclassificationidempotency_keycollector_keyexecutor_self_key_forbiddenfallback_cancel_on_successrecovery_required"recovery_is_fixed_time_or_dead_man)default_factory	List[str]reasonsc                    t        |       S N)r   selfs    r   to_dictzDispatchContractRecord.to_dictn   s    d|r   c                N    t        j                  | j                         d|      S )NFensure_asciiindent)jsondumpsr=   )r<   rA   s     r   to_jsonzDispatchContractRecord.to_jsonq   s    zz$,,.uVLLr   N)returndict)   )rA   intrE   r(   )
r   r   r   r   __annotations__r   listr8   r=   rD   r   r   r   r'   r'   \   se    ?KL!!!%% $$(,,t4GY4Mr   r'   )r1   c                    t        |       t        |      }t        |      }t        |      }g }d|  }|r,t        }t        |      }d}	|j                  d|rdndz          ng|rt        }d}d}	|j                  d       nIt
        }d}t        |      }	|j                  d       |r|j                  d       n|j                  d	       t        t        | t        |      t        |      t        |      |||d
||	d|      S )u  단일 dispatch 의 callback 계약 분류 (공개 entrypoint · 순수 함수).

    §7b 런타임 계약 1~6 을 코드로 강제한다:

      * normal callback 수신 → ``CONTRACT_OK``. fallback 은
        cancel-on-success(노이즈 0). (계약 1·3)
      * normal 미수신 · fallback 존재 → ``FALLBACK_RECOVERY`` —
        미수신 recovery 안전망(진행트리거 아님·NON_BLOCKING). (계약 2·4)
      * normal·fallback 둘 다 부재 → ``DISPATCH_CONTRACT_VIOLATION``.
        result 까지 존재(=계약 6 조건)면 recovery watcher 가 독립 ANU
        collector 를 1회 spawn 해야 한다(``recovery_required=True``).
        result 부재면 위반이되 spawn 조건 미충족(no-op·진행트리거화 0).
        (계약 5·6)

    collector_key 는 독립 ANU key 만 허용 — executor self-key 면 fail-closed.
    recovery 는 *조건 탐지* 결과일 뿐 fixed-time/dead-man 진행트리거가
    아니다(``recovery_is_fixed_time_or_dead_man`` 는 항상 False — 640665C8
    안티패턴 재발 차단의 코드 명문).

    task-2620 §2.2 H1 hardening: CONTRACT_OK edge case 입력 정규화.
    mixed-signal (예: normal_callback_present=True 인데 fallback_present
    가 None/non-bool) 도 ``bool(...)`` 으로 강제하여 truthy 잡음을 차단한다.
    truthy 비-bool 입력은 boolean 정규화 후 그대로 평가 — 약화 없음.
    zdispatch_callback_contract:Fu>   normal completion callback 수신 → CONTRACT_OK (계약 1). uP   fallback durable-success 즉시 cancel-on-success 제거(노이즈 0·계약 3).u;   fallback 미등록이나 normal 수신으로 계약 충족.u   normal callback 미수신 · fallback safety-net 존재 → FALLBACK_RECOVERY (계약 2·4). fallback 은 미수신 recovery 전용·NON_BLOCKING — 진행 트리거 아님(fixed-time/dead-man 금지).ua   normal callback · fallback safety-net 둘 다 부재 → DISPATCH_CONTRACT_VIOLATION (계약 5).u   result 존재 + normal 미수신 + fallback 미수신 — recovery watcher 가 독립 ANU collector 를 idempotent 1회 spawn 해야 함 (계약 6). 무조율 dead-man/fixed-time 진행트리거 금지 (640665C8 안티패턴 차단).u~   result 부재 — 계약 6 spawn 조건 미충족. recovery watcher no-op(진행트리거화 0 · fixed-time/dead-man 아님).T)r)   r*   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r8   )r$   r+   r   appendr   r   r'   CONTRACT_SCHEMA)
r*   r,   r-   r.   r1   r8   r0   r/   r3   r4   s
             r   classify_dispatch_contractrN   u   sD   @ ,M:
 ##:;,-.)NG3G9=O$%)*:%;"!L $  S	
 
*%*"!\	
 5%*" !06	
 NN2 NNM
 " $%< =./N+%'#$(#=++0 r   c                      e Zd ZdZy)InvalidObservationu   task-2620 §2.2 H2 — evaluate input coercion fail-closed.

    빈 dict · None · wrong type · required-key 누락 입력을 받았을 때
    silent normalization 대신 즉시 명시 예외를 발생시킨다. 약화 우회 0.
    Nr   r   r   r   rP   rP      r   r   rP   )r,   r-   r.   )r*   c                   | t        d      t        | t              s"t        dt        |       j                   d      | st        dt
         d      t
        D cg c]	  }|| vs| }}|rt        d| d      t        D ]X  }| |   }|t        d|d	      t        |t        t        t        f      r5t        d|d
t        |      j                   d       t        | j                  dd            }t        | j                  dt                    }t        |t        | j                  dd            t        | j                  dd            t        | j                  dd            |      S c c}w )u@  실 entrypoint: observation dict(또는 fixture) → 계약 레코드.

    mock-only 경로는 본 함수를 우회할 수 없다 — 실제 관측값을 분류한다.

    task-2620 §2.2 H2 hardening: input-coercion fail-closed.

      * ``observation is None`` / dict 아님 / 빈 dict → InvalidObservation.
      * required key (task_id + 3 boolean signal) 부재 → InvalidObservation.
      * boolean signal 이 None / non-bool non-truthy-coerce 가능 type → 즉시
        InvalidObservation 으로 차단 (실수로 dict / list 가 들어오면 정상
        bool(...) 이 항상 True 가 되어 분류가 왜곡되는 경로 봉합).

    기존 PASS-path (정합 fixture · dict 입력) 는 byte-0 — 어느 fixture 도
    실패하지 않는다(H2 regression 케이스 + 기존 cases (a)~(f) 둘 다 PASS).
    uW   observation is None — dispatch contract 평가에는 dict 가 필요 (H2 fail-closed)zobservation must be dict, got u     — wrong type fail-closed (H2)u5   observation is empty dict — required keys missing (u   ) — H2 fail-closedz%observation missing required key(s): u    — H2 fail-closedzobservation[u6   ] is None — boolean signal required (H2 fail-closed)z] type u4    is not bool/int/str — wrong type fail-closed (H2)r*   ztask-unknownr1   r,   Fr-   r.   r*   r,   r-   r.   r1   )rP   
isinstancerF   typer   _OBSERVATION_REQUIRED_KEYS_OBSERVATION_BOOL_KEYSr+   rH   r(   getr"   rN   )observationkmissingvr*   r1   s         r   evaluater\      s      
 	
 k4( ,T+->-G-G,H I* *
 	
  *++?A
 	
 5MQ8LqMGM 3G9<OP
 	
 $ N 9$qe $# #  !dC-.$qe747+;+;*< =? ?  +//)^<=G)<=M & $OO5u=!
 koo.@%HIKOO,<eDE# 1 Ns   	E4'E4c                  h    e Zd ZdZedd	 	 	 	 	 	 	 d	dZ	 	 	 	 d
dZddZ	 	 	 	 ddZe	dd       Z
y)RecoveryWatcheru7  DISPATCH_CONTRACT_VIOLATION(result+normal-missing+fallback-missing)
    조건에서만 독립 ANU collector 를 **idempotent** 하게 1회 spawn.

    데몬 안전원칙 (§7b.7):

      * **sense**: ``observe`` 는 레코드를 분류하기만 한다 — 어떤 상태도
        변경하지 않는다(부수효과 0).
      * **act**: ``maybe_spawn`` 은 조건 충족 시에만 *정확히 1회* 주입된
        ``spawn_fn`` 을 호출하고 idempotency_key 를 self-record 하여
        재진입을 차단한다.
      * 무조율 dead-man / fixed-time 진행트리거 절대 금지(640665C8 차단).
        조건 미충족이면 **no-op** — 시간 경과만으로 발화하는 경로가 코드에
        존재하지 않는다.
      * spawn 은 독립 ANU key 로만 — executor self-key fail-closed.
    N)r1   seenc               h    t        |       || _        || _        ||| _        y t               | _        y r:   )r$   	_spawn_fn_collector_keyset_spawned)r<   spawn_fnr1   r_   s       r   __init__zRecoveryWatcher.__init__F  s/     	0>!+%)%5T35r   c                    t        |      S )u@   관측만 — 상태 변경 0. 분류 레코드를 반환한다.)r\   )r<   rX   s     r   observezRecoveryWatcher.observeT  s     $$r   c                    |j                   t        k(  xr8 |j                  xr* |j                   xr |j                   xr |j
                  S )u   계약 6 조건: result 존재 ∧ normal 미수신 ∧ fallback 미수신
        (= DISPATCH_CONTRACT_VIOLATION ∧ recovery_required).)r/   r   r.   r,   r-   r4   )r<   recs     r   _spawn_condition_metz$RecoveryWatcher._spawn_condition_metZ  sZ     "== &""&///& (((& %%	
r   c                   | j                  |      }d|j                  |j                  d| j                  dd}| j	                  |      sd|d<   |S |j                  | j
                  v rd|d<   d|d<   |S | j
                  j                  |j                         	 | j                  |j                  | j                        }|j                  d|d	       |S # t        $ r' | j
                  j                  |j                          w xY w)
u   조건 충족 시에만 독립 ANU collector 를 1회 spawn (idempotent).

        반환: {spawned, classification, idempotency_key, duplicate_suppressed,
        reason}. 조건 미충족이면 spawned=False(no-op·진행트리거화 0).
        F)spawnedr/   r0   duplicate_suppressedr1   fixed_time_or_dead_manu|   spawn 조건 미충족(result+normal-missing+fallback-missing 아님) — no-op. fixed-time/dead-man 진행트리거 아님.reasonTrn   uG   idempotency_key 이미 spawn 됨 — 중복 spawn 0 (재진입 차단).u   DISPATCH_CONTRACT_VIOLATION(result+normal-missing+fallback-missing) — 독립 ANU collector 1회 spawn. 무조율 dead-man/fixed-time 진행트리거 아님(640665C8 차단). atomic mark-then-act (H3) — 동시/재시도 경계 중복 0.)rm   spawn_resultrp   )rh   r/   r0   rb   rk   rd   addra   r*   BaseExceptiondiscardupdate)r<   rX   rj   outrq   s        r   maybe_spawnzRecoveryWatcher.maybe_spawnf  s     ll;'!00"22$)!00&+"
 ((-Q M J$--/*.C&'Y M J 	#--.	>>#++t7J7JKL 	

%R	 	 		
 
  	 MM!!#"5"56		s   &C 0Dc                ,    t        | j                        S r:   )sortedrd   r;   s    r   spawned_keyszRecoveryWatcher.spawned_keys  s    dmm$$r   )re   SpawnFnr1   r(   r_   zOptional[set]rE   NonerX   Dict[str, object]rE   r'   )rj   r'   rE   r+   )rX   r~   rE   r~   )rE   r7   )r   r   r   r   r"   rf   rh   rk   rw   propertyrz   r   r   r   r^   r^   5  s    ( 1"AA 	A
 A 
A%,%	%	
<,<	<| % %r   r^   c                
   g }t        dddd      }|j                  t        k7  s|j                  s+|j	                  d|j                   d|j                          t        dddd      }|j                  t
        k7  s|j                  r+|j	                  d|j                   d	|j                          | xs d
}t        |      }|j                         s,t        t        j                  j                  dd            |z  }	 t        j                  |j                  d            }t'        |j                  d|            }|j                  t(        k7  s|j                  s+|j	                  d|j                   d	|j                          g t+        fd      }	|	j-                  |j                  d|            }
|	j-                  |j                  d|            }|
d   r|d   st/              dk(  s#|j	                  dt/               d|
 d|        g t+        fd      }|j-                  ddddd      }|d   sr|j	                  d|        d}	 t        ddddt0                |s|j	                  d!       |j                  |j                  k7  }d| |t%        |      |g d"d#S # t        $ r3}dddt!        |      j"                   d| t%        |      dcY d}~S d}~ww xY w# t2        $ r d}Y w xY w)$uM  실 entrypoint regression. 회장 필수 fixture(callback-gap)를 read-only
    consume 하여 DISPATCH_CONTRACT_VIOLATION + recovery 경로를 실증한다.

    mock-only 경로는 본질적으로 실패한다 — 상수 분류기는 (a)CONTRACT_OK 와
    (c)DISPATCH_CONTRACT_VIOLATION 을 동시에 만족시킬 수 없다.
    ztask-selftest-aT)r*   r,   r-   r.   z(a) z fc=ztask-selftest-bFz(b) z rr=z0memory/fixtures/task-2614.case-callback-gap.jsonWORKSPACE_ROOTz/home/jay/workspaceutf-8encodingz/anu_v3.dispatch_callback_contract.self_check.v1zfixture read fail: z: )r)   
all_passederrorfixtureNrX   z(c) c                .    j                  |       xs dS )Nrm   rL   )tid_keycallss     r   <lambda>z run_self_check.<locals>.<lambda>  s    %,,s*;*Hy r   rm      z(d) spawns=z r1=z r2=c                &    j                  |       S r:   r   )r   r   calls_es     r   r   z run_self_check.<locals>.<lambda>  s    GNN3,? r   ztask-selftest-ez(e) unexpected spawn noop=ztask-selftest-frR   z%(f) executor self-key NOT fail-closed)abcdef)r)   r   failuresr   mock_only_would_failcases)rN   r/   r   r3   rL   r   r4   r   is_absoluteosenvironrW   rB   loads	read_text	ExceptionrT   r   r(   r\   r   r^   rw   lenr!   r   )fixture_pathr   r   r   fxfxpobsexcr   wr1r2w_enoopself_key_fail_closedr   r   r   s                   @@r   run_self_checkr     s    H 	#! $		A 	;&a.J.J$q//0Q5Q5Q4RST 	#! %		A 	,,0C0C$q//0Q5H5H4IJK 
 
:  r(C??2::>>"24IJKbP
jj89 	,-A66a>Q>Q1##$D)<)<(=>	

 EHIA	
sww}c2	3B	
sww}c2	3ByM"Y-CJ!O+c%j\bTbTBC G
?
@C??('+ %"		
D I'4TF;< !	$"%$(!5	
  ?@ ++q/?/?? D"ls8 4/ k  
G*49+=+=*>bF3x	
 	

Z $ $#$s0   %J5  K4 5	K1>(K,&K1,K14LLc                     t        j                  dd      } | j                  ddd       | j                  dd d	
       | j                  dd d
       | S )Ndispatch_callback_contractu{   task-2614 §7b — dispatch callback/progress-trigger 계약 런타임 (회장 야간 필수 보강 · 문서-only 금지).)progdescriptionz
--selftest
store_trueu    실 entrypoint regression 실행)actionhelpz	--fixtureu;   callback-gap fixture 경로 (기본: 회장 필수 fixture))defaultr   z
--evaluateu0   observation JSON 경로 → 계약 분류 출력)argparseArgumentParseradd_argument)ps    r   _build_parserr     sl    )I	A
 NN<:  <NN;U  WNN<J  LHr   c                   t               j                  |       }|j                  rmt        j                  t        |j                        j                  d            }t        |j                  d|            }t        |j                                yt        |j                        }t        t        j                  |dd             |j                  d      rdS d	S )
Nr   r   rX   r   FrG   r?   r   r   )r   
parse_argsr\   rB   r   r   r   rW   printrD   r   r   rC   )argvargsr   rj   ress        r   _mainr     s    ?%%d+D}}jjdmm,666HIsww}c23ckkm

&C	$**SuQ
78%1,1,r   __main__)r#   r(   rE   r|   )r*   r(   r,   r+   r-   r+   r.   r+   r1   r(   rE   r'   r}   r:   )r   zOptional[str]rE   r~   )rE   zargparse.ArgumentParser)r   zOptional[Sequence[str]]rE   rH   ).r   
__future__r   r   rB   r   dataclassesr   r   r   pathlibr   typingr   r	   r
   r   r   rM   r"   r!   r   r   r   r   CLASSIFICATIONSRuntimeErrorr   r   r$   r'   rN   
ValueErrorrP   rV   rU   r\   r(   objectr{   r^   r   r   r   r   
SystemExitr   r   r   <module>r      si  : #   	 0 0  ; ;8 ) 0  ; % '  	; ;| 
$ $M M M< -gg "g 	g
 g g gT  
 *,BB ?F C:v%
&q% q%jbJ	- z
UW
 r   