
    j1M                    @   d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m	Z	m
Z
 ddlmZmZmZmZmZ ddlmZmZmZmZmZmZmZmZmZmZmZ dZeZd	Zd
Z dZ! e"h d      Z# G d de$      Z%ddZ&	 d 	 	 	 	 	 d!dZ'd"dZ(ee ejR                  jT                  dddddddddf	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d#dZ+d$dZ,e G d d             Z-d%d&dZ.d'dZ/	 d 	 	 	 	 	 	 	 	 	 	 	 	 	 d(dZ0d)dZ1deede!ddf	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d*dZ2d+dZ3	 	 	 	 	 	 d,dZ4y)-um  utils.anu_callback_registrar — ANU normal completion callback registrar.

task-2635 — normal callback registration enforcement (Core hardening).
task-2635+1 — build-then-register-then-update + 5축 status 정정.

Spec: memory/specs/system_normal_callback_registration_implementation_spec_260523.md
sha256: 0fbd1dad1e110c49474dfbdf13a21fb3bdd9c7f094128004dba8472840bb832d

회장 verbatim (task-2635 §3.1):
    utils/anu_callback_registrar.py — 순수함수
      * build_callback_envelope(task_id, result, anu_key, collector_role="ANU")
      * register_normal_callback(envelope, delay_seconds=10)
      * 내부: cokacdir --cron "..." --at "T+10s" --chat 6937032012 --key <ANU>
      * ANU key hardcoded fail-closed (c119085addb0f8b7) — self-key 차단
      * envelope UTF-8 byte 측정 + ≤3900 hard limit

회장 verbatim (task-2635+1 §2/§4):
    REGISTERED 는 실제 schedule_id 생성 후에만 기록 · envelope 최종 5축 등록
    결과 정확 반영 (register-after-update 패턴).

ANCHOR-1 (task-2635+1): "envelope payload 최종 5축 상태 정확 반영 —
build-then-register-then-update 순서".
    )annotationsN)	dataclassfield)datetimetimezone)AnyCallableDictListOptional)ANU_CALLBACK_KEYENVELOPE_BYTE_LIMITENVELOPE_WARN_LOWERENVELOPE_WARN_UPPERCallbackDeliveryStatusCollectorReceiptStatusDeliveryMethod NormalCallbackRegistrationStatusRegistrationResultStatusenvelope_utf8_byte_countvalidate_envelopezutils.anu_callback_registrar.v2
6937032012ANUz/usr/local/bin/cokacdir>   dev1dev2dev3dev4dev5dev6dev7dev8c                      e Zd ZdZy)SelfKeyForbiddenux   Raised when the caller tries to use the executor's own bot key as the
    independent ANU collector key (spec §7).
    N)__name__
__module____qualname____doc__     3/home/jay/workspace/utils/anu_callback_registrar.pyr#   r#   =   s    r)   r#   c                j    | syt         D ]'  }d|j                          } ||      }|s!|| k(  s' y y)NFCOKACDIR_KEY_T)SELF_KEY_DENYLIST_HINTSupper)candidate_key
env_lookupbotenv_namevs        r*   _is_self_key_matchr4   C   sE    & "399;-0x m#	
 r)   c                    | st        d      | t        k7  rt        d| dt        d      |dd l}|j                  j                  }t        | |      rt        d      y )NzANU key is emptyzANU key drift: got z% but hardcoded INDEPENDENT_ANU_KEY = u    (spec §7)r   zANU key collides with an executor self-key (COKACDIR_KEY_DEV*); the independent ANU key must NEVER be the executor's own bot key.)r#   INDEPENDENT_ANU_KEYosenvirongetr4   )anu_keyr0   _oss      r*   _assert_independent_anu_keyr<   N   s}     122%%!' -%%8$;;H
 	
 [[__
':.P
 	
 /r)   c                 f    t        j                  t        j                        j	                  d      S )Nz%Y-%m-%dT%H:%M:%SZ)r   nowr   utcstrftimer(   r)   r*   _now_isorA   f   s!    <<%../CDDr)   Tc                    | rt        | t              st        d      t        |t              st	        d      t        |       |t        k7  rt        dt        d|      |	t        j                  j                  }	|t        |      }||	t        j                  j                  k(  rt        j                  j                  }n|	t        j                  j                  k(  rt        j                  j                  }n|	t        j                   j                  t        j"                  j                  fv rt        j$                  j                  }n7|rt        j&                  j                  }nt        j$                  j                  }||	t        j                  j                  k(  rt(        j*                  j                  }n|	t        j                  j                  k(  r|rt(        j*                  j                  }n|	t        j                  j                  t        j                  j                  t        j                   j                  t        j"                  j                  fv rt(        j$                  j                  }nt(        j*                  j                  }i dt,        d| d|j/                  dd      d	|j/                  d	d
      d|j/                  dd
      dt        |
      dt        |      d|	d|d|dt        |      d|	d|d|d|dt1               d|j/                  dd
      |j/                  dd
      |j/                  dd
      |j/                  dd
      d}|r||d<   |r||d<   |r||d<   |S )uu  Compose a normal completion callback envelope (spec §3.1 / §4.2).

    task-2635+1 §2: this is the **seed** envelope. The status axes default to
    a pre-attempt state (registration_result_status=NOT_REGISTERED,
    delivery=PENDING, receipt=UNCONFIRMED). The registrar transitions them
    *after* a real schedule_id is observed (build-then-register-then-update).
    z"task_id must be a non-empty stringzresult must be a dictzcollector_role must be u    (spec §3.4); got schematask_idexecutor_namezdispatch-executorresult_path report_pathregistration_intentregistration_attemptedregistration_result_statuscallback_delivery_statuscollector_receipt_statusattempted_callback_registrationregistration_statusdelivery_methodcollector_roler:   envelope_built_at
commit_shafile_summaryregression_summaryspec_sha256)rT   rU   rV   cron_schedule_iderror_messageexplicit_skip_reason)
isinstancestr
ValueErrordict	TypeErrorr<   COLLECTOR_ROLE_ANUr   NOT_REGISTEREDvaluebool
REGISTEREDr   	DELIVEREDREGISTER_FAILEDUNDELIVEREDSENDFILE_ONLYSKIPPED_WITH_EXPLICIT_REASONNOT_APPLICABLEPENDINGr   UNCONFIRMEDREGISTRAR_SCHEMAr9   rA   )rD   resultr:   rQ   rP   rN   rW   rX   rY   rO   rI   rJ   rL   rM   envelopes                  r*   build_callback_envelopero   j   s   . *Wc2=>>fd#/00(++%&8%; <!$&
 	
 "6EEKK %!%&E!F  '":"E"E"K"KK'='G'G'M'M$ $<$L$L$R$RR'='I'I'O'O$ $2288$AAGG%
 
 (>'L'L'R'R$#'='E'E'K'K$'='L'L'R'R$'":"E"E"K"KK'='I'I'O'O$ $<$K$K$Q$QQVl'='I'I'O'O$ $3399$44::$2288$AAGG	%
 
 (>'L'L'R'R$'='I'I'O'O$ " 7  	O5HI  	vzz-4	 
 	vzz-4  	t$78  	!$'=">  	%&9  	#$<  	#$<  	*40O+P  	2  	?   	.! " 	7# $ 	XZ% & 	fjjr2' ( 

>26$jj)=rBzz-4- H0 '7#$$1!+?'(Or)   c                n    t        |       }t        |cxk  r	t        k  rn yd| dt         dt         dS y)zHReturn a soft warning string if envelope is in the warn band, else None.envelope size z bytes in warn band (~)N)r   r   r   )rn   ns     r*   envelope_byte_warningru      sC     *Aa6#66  s"78K7LANaMbbcddr)   c                      e Zd ZU ded<   dZded<   dZded<   dZded<    ee      Z	d	ed
<   dZ
ded<   dZded<   dZded<   y)RegistrarResultr[   statusNOptional[str]schedule_idregistered_at_tserror)default_factory	List[str]argvr   int
byte_countdelivery_statusreceipt_status)r$   r%   r&   __annotations__rz   r{   r|   r   listr   r   r   r   r(   r)   r*   rw   rw      sS    K!%K%&*m*E=D1D)1J%)O])$(NM(r)   rw   c                4    ddl }|j                  | dd|      S )z?Real subprocess runner. Regression tests inject a fake instead.r   NT)capture_outputtexttimeout)
subprocessrun)r   r   _sps      r*   _default_runnerr      s    77447IIr)   c                Z    t        dt        |             }|dk  r| dS |dz   dz  }| dS )uQ  Convert delay seconds to a ``cokacdir --at`` value.

    task-2661 Phase 2b live-runtime correction (docstring only — signature /
    body unchanged per chair verbatim ANCHOR-4):

      cokacdir live runtime accepts minute/hour/day suffixes
      (``10m`` / ``4h`` / ``1d``) AND absolute timestamps
      (``YYYY-MM-DD HH:MM:SS``). The second suffix (``10s``) is
      **UNSUPPORTED in live runtime** — cokacdir rejects with
      ``Error: invalid --at value: 10s`` (chair-paste evidence
      2026-05-25 15:33, task-2660 incident). For sub-minute fires, use an
      absolute timestamp (``1m`` is the smallest legal relative-suffix form).

    Tests that pin ``--at 10s`` use ``_build_cokacdir_cron_argv`` with a
    custom ``at_value`` override AND a fake subprocess runner — they do not
    exercise the live cokacdir runtime, so the rejected form is harmless
    inside the unit-test environment only. Production callers wanting a
    sub-minute fire MUST pass an absolute timestamp via ``at_value`` (see
    ``dispatch.normal_fallback_callback_helper.build_absolute_at_for_normal_delay``).
    r   <   s;   m)maxr   )delay_secondsr   minutess      r*   _delay_to_at_valuer      s@    * 	As=!"A2vAw2v"nGYa=r)   c           
     n    t        j                  | dd      }||nt        |       d}|d|d|d|d|d	g
S )
zBuild the ``cokacdir --cron`` argv. ``at_value`` overrides the derived
    delay grain when the caller needs to pin a specific form (e.g. tests use
    ``10s`` literal; live registrations use ``_delay_to_at_value`` minutes).FT)ensure_ascii	sort_keysr   z--cronz--atz--chatz--keyz--once)jsondumpsr   )rn   r   chat_idr:   cokacdir_pathat_valueprompt	chosen_ats           r*   _build_cokacdir_cron_argvr     sW     ZZuEF$0]9K8LA6NI r)   c                N   | sy 	 t        j                  | j                         j                         d         }t        |t              sy |j                  d      dk7  ry |j                  d      xs |j                  d      }|rt        |      S d S # t        t
        f$ r Y y w xY w)Nrx   okidrz   )
r   loadsstrip
splitlinesr\   
IndexErrorrZ   r]   r9   r[   )stdoutpayloadsids      r*   _parse_schedule_idr   1  s    **V\\^668<= gt${{8$
++d

9w{{=9C3s8$$ 
# s   4B B$#B$
   c           	        |xs t         }t        |       \  }	}
|	sf|
rdt        t        j                  j
                  d|
 t        |       t        j                  j
                  t        j                  j
                        S 	 t        |       t        |       }|t        kD  rct        t        j                  j
                  d| dt         d|t        j                  j
                  t        j                  j
                        S ||nd } ||      s[t        t        j                  j
                  d	| |t        j                  j
                  t        j                  j
                        S t        | |||||
      }	  ||d      }t!        |dd      }t!        |dd      xs d}t!        |dd      xs d}|dk7  rpt        t        j                  j
                  d| d|j#                         dd  ||t        j                  j
                  t        j                  j
                        S t%        |      }|smt        t        j                  j
                  d|j#                         dd  ||t        j                  j
                  t        j                  j
                        S t        t        j&                  j
                  |t)               ||t        j*                  j
                  t        j,                  j
                        S # t        $ rn}t        t        j                  j
                  d| t        |       t        j                  j
                  t        j                  j
                        cY d}~S d}~ww xY w# t        $ rf}t        t        j                  j
                  d|||t        j                  j
                  t        j                  j
                        cY d}~S d}~ww xY w)u  Register the normal completion callback via ``cokacdir --cron``.

    task-2635+1 §2/§4: the **input** envelope carries the seed (axis-3 =
    NOT_REGISTERED, delivery=PENDING, receipt=UNCONFIRMED). The returned
    RegistrarResult carries the real outcome — including ``delivery_status``
    and ``receipt_status`` derived from the registration result. Callers
    propagate those into the final envelope via
    ``merge_registrar_result_into_envelope`` (build-then-register-then-update).
    zenvelope schema invalid: )rx   r|   r   r   r   z'self-key/independent-key guard failed: Nrq   z bytes > hard limit u    (spec §3.1)c                X    t        t        j                  |       xs t        |             S N)rb   shutilwhich_path_exists)ps    r*   <lambda>z*register_normal_callback.<locals>.<lambda>{  s    $v||A9,q/: r)   zcokacdir CLI not found at )rn   r   r   r:   r   r      zsubprocess raised: )rx   r|   r   r   r   r   
returncode   r   rG   stderrr   zcokacdir exit=z stderr=i   z(schedule_id missing in cokacdir stdout: )rx   rz   r{   r   r   r   r   )r   r   rw   r   re   ra   r   r   rf   r   ri   r<   r#   r   r   	Exceptiongetattrr   r   rc   rA   rd   rk   )rn   r   r   r:   subprocess_runnerr   cli_exists_checkr   runnerr   errorsexcr   	cli_checkr   procrcr   r   rz   s                       r*   register_normal_callbackr   @  s   & 1/F"8,JB &+;;AA-fX6/92>>DD1@@FF
 	
	
#G, *(3J''+;;AA ,@&'}6 "2>>DD1@@FF	
 		
 %5$@ :  ]#+;;AA.}o>!2>>DD1@@FF
 	
 %##D

dB 
|Q	'BT8R(.BFT8R(.BF	Qw+;;AA"2$hv||~ds/C.DE!2>>DD1@@FF
 	
 %V,K+;;AA<V\\^DS=Q<RS!2>>DD1@@FF
 	
 '2288!.88>>-99?? c  
+;;AA;C5A/92>>DD1@@FF
 	

Z  
+;;AA'w/!2>>DD1@@FF
 	

s>   L	  	N 		N A#M;5N ;N 	O2AO-'O2-O2c                @    dd l }|j                  j                  |       S )Nr   )r7   pathexists)r   r;   s     r*   r   r     s    88??4  r)   c                   t        |       }|j                  |d<   |j                  |d<   |j                  r|j                  |d<   |j                  r|j                  |d<   |j                  r|j                  |d<   |j
                  r|j
                  |d<   |j                  r|j                  |d<   |j                  xs t        |      |d<   |S )	u  Immutable register-after-update pattern (task-2635+1 §4).

    Returns a NEW envelope with all 5 axes synced to the registrar outcome:
      * axis-3 registration_result_status (+ legacy registration_status alias)
      * cron_schedule_id / registered_at_ts (when present)
      * axis-4 callback_delivery_status   (from rr.delivery_status)
      * axis-5 collector_receipt_status   (from rr.receipt_status)
      * error_message on failure
      * envelope_utf8_bytes audit field
    Input envelope is NEVER mutated.
    rK   rO   rW   r{   rL   rM   rX   envelope_utf8_bytes)	r]   rx   rz   r{   r   r   r|   r   r   )rn   rrouts      r*   $merge_registrar_result_into_enveloper     s     x.C(*		C$%!#C	~~"$..	"$"5"5	*,*<*<&'	*,*;*;&'	xx!xxO!#!O2J32OCJr)   )r/   r[   r0   zCallable[[str], Optional[str]]returnrb   r   )r:   r[   r0   z(Optional[Callable[[str], Optional[str]]]r   None)r   r[   )rD   r[   rm   r   r:   r[   rQ   r[   rP   r[   rN   rb   rW   ry   rX   ry   rY   ry   rO   ry   rI   rb   rJ   zOptional[bool]rL   ry   rM   ry   r   Dict[str, Any])rn   r
   r   ry   )r   )r   r~   r   r   r   z'subprocess.CompletedProcess')r   r   r   r[   )rn   r   r   r   r   r[   r:   r[   r   r[   r   ry   r   r~   )r   r[   r   ry   )rn   r   r   r   r   r[   r:   r[   r   z)Optional[Callable[[List[str], int], Any]]r   r[   r   zOptional[Callable[[str], bool]]r   ry   r   rw   )r   r[   r   rb   )rn   r   r   rw   r   r   )5r'   
__future__r   r   r   r   dataclassesr   r   r   r   typingr   r	   r
   r   r   utils.callback_envelope_schemar   r   r   r   r   r   r   r   r   r   r   rl   r6   DEFAULT_CHAT_IDr_   COKACDIR_CLI	frozensetr-   r\   r#   r4   r<   rA   ANU_CRON_CALLBACKra   ro   ru   rw   r   r   r   r   r   r   r   r(   r)   r*   <module>r      s  . #    ( ' 6 6    5  '  (#D 
z  JN

F
	
0E ',);;AA,0&*#'*.)- $-1.2.2nnn n 	n
 n &*n $n !n (n 'n n +n ,n ,n nb 	) 	) 	)JD #  	
   6%" "&CG%8<"}}} } 	}
 A} } 6} } }@!"1r)   