
    jL                    r   d Z ddlmZ ddlmZ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 ddlmZ ddlmZmZ ddlmZmZ dd	lmZ dd
lmZmZmZ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*dZ+dZ,dZ-dZ.dZ/dZ0dZ1dZ2dZ3 e4e%e&e'e(e)e*e+e,e-e.e/e0h      Z5dZ6dZ7 e4ddh      Z8d Z9d0d!Z:d1d"Z;d2d#Z<d$d$d%	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d3d&Z=	 	 	 	 	 	 	 	 	 	 d4d'Z>d5d(Z?d6d)Z@ed$d$d$d*d$d$d+	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d7d,ZAd$d$d$d$d*d$d$d-	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d8d.ZBg d/ZCy$)9u+  utils.real_merge_hooks — dryrun→real switch + real_merge_execute() 순수함수.

task-2637 — real merge executor wiring 코드화 (activation false default · 실제 merge 실행 0).

Spec: memory/specs/system_real_merge_executor_wiring_spec_260523.md §5 + §1
sha256: bcaf654e981a43083af50879164021c918eeac9753cad3b3ad146209a1a62765

회장 verbatim (회장 10결정 #8):
    dryrun→real switch 위치 = 신규 real_merge_hooks 모듈
    finalize_hooks 직접 확장 최소화

회장 verbatim (회장 10결정 #1):
    activation = env var + chair_authorization JSON 둘 다 필요.
    둘 중 하나라도 없으면 NO_OP_NO_AUTHORIZATION.

회장 verbatim (회장 10결정 #4):
    post-merge smoke 실패 시 자동 rollback 금지. 보고 only.

회장 verbatim (회장 10결정 #6):
    artifact 위치 = memory/events/real_merge/<pr>/<head_sha>/

ANCHOR-3 (task md): "non-admin merge 만 허용 · admin override 코드 경로 0 ·
gh pr merge 실호출 0 (subprocess injection mock)"

ANCHOR-6 (task md): "forbidden paths 11+종 정적 가드 (replacement_pr_runner/
finish-task.sh/merge_ready_classifier/merge_ready_dryrun_executor/
callback_envelope_schema/anu_callback_registrar/canonical_root_resolver/
anu_collector_action_trigger/dispatch_finalize_hooks/.tasks/tests/fixtures/
tests/regression)"
    )annotations)datetime	timedeltatimezone)AnyCallableDictListOptional)ACTIVATION_FLAG_DEFAULTassert_default_offresolve_activation_flag)validate_chair_authorization)
GATE_STALEvalidate_gate_snapshot)ACTION_WOULD_MERGESCHEMA_AUTO_MERGE_CANDIDATE)PASS)EXECUTION_RESULT_SCHEMAMERGE_DECISION_SCHEMAartifact_already_presentwrite_merge_decisionwrite_merge_execution_resultwrite_pre_merge_gate_snapshot)ALLOW_REASON_SNAPSHOT_CROSSREFvalidate_snapshot_crossrefzutils.real_merge_hooks.v2NO_OP_FLAG_DISABLEDNO_OP_NO_AUTHORIZATIONNO_OP_NOT_PASSNO_OP_DRYRUN_MISMATCHNO_OP_GATE_FAILNO_OP_STALE_SNAPSHOTNO_OP_DUPLICATENO_OP_FORBIDDEN_PATHNO_OP_AUTH_MISMATCH%CHAIR_REQUIRED_PRODUCTION_IN_SNAPSHOT*CHAIR_REQUIRED_BLOCKING_SECRET_IN_SNAPSHOT&CHAIR_REQUIRED_ADMIN_OVERRIDE_REQUIREDREAL_MERGE_DONEREAL_MERGE_FAILEDPOST_MERGE_SMOKE_FAILED)
zutils/replacement_pr_runner.pyzscripts/finish-task.shzutils/merge_ready_classifier.pyz$utils/merge_ready_dryrun_executor.pyz!utils/callback_envelope_schema.pyzutils/anu_callback_registrar.pyz utils/canonical_root_resolver.pyz%utils/anu_collector_action_trigger.pyzdispatch/finalize_hooks.pyzdispatch/__init__.py)z.tasks/ztests/fixtures/ztests/regression/z--adminz--force<   c                    | dgS | sg S g }| D ]\  }t        |t              s|t        v r|j                  |       .t        D ]&  }|j                  |      s|j                  |        \ ^ |S )zReturn the subset of changed_files that hit the forbidden list.

    Used by real_merge_execute before any other check so a contaminated PR
    short-circuits to NO_OP_FORBIDDEN_PATH (+ CHAIR_REPORT) without ever
    reaching the gate snapshot stage.
    __INPUT_NONE_FAIL_CLOSED__)
isinstancestrFORBIDDEN_PATHSappendFORBIDDEN_DIR_PREFIXES
startswith)changed_fileshitspathprefixs       -/home/jay/workspace/utils/real_merge_hooks.pydetect_forbidden_pathsr:      s     ,--	D 	$$?"KK, 	Fv&D!		 K    c                     t        j                  t        j                        j	                  t        t        d                  j                  d      S )N	   )hoursz%Y-%m-%dT%H:%M:%S+09:00)r   nowr   utc
astimezoner   strftime r;   r9   _now_kstrD      s6    X\\"	HYQ/0	1	+	,r;   c                V   t        | t              r| ni }|j                  d      }	 |t        |      nd}|j                  dd      }t        |t              r|nd}|||j                  dd      |j                  dd      |j                  dd      dS # t        t
        f$ r d}Y qw xY w)	Nprr   head_sha task_idbranchbase_sha)rF   rG   rI   rJ   rK   )r/   dictgetint	TypeError
ValueErrorr0   )pr_identitypiraw_prsafe_prraw_head	safe_heads         r9   _identity_or_defaultrW      s    ";52BVVD\F!'!3#f+ vvj"%H&x52I66)R(&&2&FF:r* 	 z" s   B B('B(N)allow_reasonsnapshot_crossrefc       
            t         ||xs
 t               | |xs d|xs dt        |      t        |      |t        |      |	|
d}|S )uz  Build a merge_decision payload (schema v2).

    task-2639 (spec §5): v1 → v2 bump. ``allow_reason`` + ``snapshot_crossref``
    필드 추가. 기존 callers 는 두 필드를 명시하지 않으면 ``None`` 기본값으로
    누락 (json 직렬화 시 ``null``) — v1 fixture 와 차이는 두 추가 필드뿐이며
    legacy 검증 키는 그대로 유지된다.
    rH   )schemaactually_executedts_kstrQ   verdict_inputdryrun_action_inputactivation_flagchair_authorization_presentresult_enumreasonsrX   rY   )r   rD   boollist)rQ   rb   r`   ra   dryrun_actionverdictrc   r]   r\   rX   rY   payloads               r9   _build_decisionri      sU    , (.&HJ" B,20'+,G'H"=$.G Nr;   c               r    |sy | j                  d      }| j                  d      }||sy t        ||||      S )NrF   rG   )rM   r   )rQ   decisioncanonical_rootwrite_artifactsrF   rG   s         r9   _persist_decisionrn      sA     		Bz*H	zHhGGr;   c                >    | D cg c]  }|t         v s| c}S c c}w )u   Return any forbidden CLI tokens observed in argv.

    The injected subprocess_runner must receive argv composed by this module
    only — so the static guard is over our composition, not the runner.
    )_FORBIDDEN_CLI_TOKENS)argvtoks     r9   _validate_runner_no_adminrs      s       @C3*?#?C@@@s   c                6    dddt        t        |             ddgS )u  Compose the ONLY allowed gh pr merge argv (non-admin, no force).

    Returns the argv but does NOT execute it. Callers MUST pass it through an
    injected subprocess_runner; the default runner is None which means "do
    not call subprocess at all" — the regression contract.
    ghrF   mergez--mergez--delete-branch=false)r0   rN   )rF   s    r9   _compose_merge_argvrw      s%     	dGSR\ r;   T)r`   subprocess_runnerrl   r5   rm   r]   clockc               J   t                t        |      }|d   }|d   }t        | t              r| j	                  d      nd}t        |t              r|j	                  d      nd}t        |||	t        t              t        t                    }|W|d   r|d   sMt        |t        t        |      d	||d
|d    d|d    g|dd|      }t        ||||
      }t        ||ddd	g dS |d   d   }|d   d   }|r\t        |t        t        |      t        |du      |||D cg c]  }d| 	 c}|dd|      }t        ||||
      }t        ||ddd	g dS |d   r|d   rVt        |t        t        |      d	||ddj                  |d         z   g|dd|      }t        ||||
      }t        ||ddd	g dS |d   rVt        |t         t        |      d	||ddj                  |d         z   g|dd|      }t        ||||
      }t         ||ddd	g dS |rt"        nd}t        |      sCt        |t$        dt        |du      ||dg|d||      }t        ||||
      }t$        ||dddg dS t'        |||      \  }}}|s[t        |t(        d	t        |du      |||D cg c]
  }d| d|  c}xs |g|d||      }t        ||||
      }t(        ||dddg dS |t*        k7  r<t        |t,        d	d	||d|dg|d||      }t        ||||
      }t,        ||dddg dS |t.        k7  rBt        |t0        d	d	||d |d!t.        g|d||      }t        ||||
      }t0        ||dddg dS t        |t              ri|j	                  d"      t2        k7  rQt        |t0        d	d	||d#|j	                  d"      d!t2        g|d||      }t        ||||
      }t0        ||dddg dS t5        ||      \  }}}|s|t6        k(  rt8        nt:        } d}!t        |t              r_|j	                  d$g       xs g D ]F  }"t        |"t              s|"j	                  d%      d&k(  s)t        |"j	                  d'            sDd	}! n | t:        k(  r|!r|d   rt<        } d}#t        |t              r|j	                  d$      ng }$h d(}%t        |$t              r?|$D ]9  }"t        |"t              s|"j	                  d%      }&|&|%vr+|&d&k(  rt        |"j	                  d'            rd	}#M|&d)k(  r*|"j	                  d'      d*k7  s|"j	                  d+      d,vrd	}#||&d-k(  r|"j	                  d'      du rd	}#|&d.k(  r|"j	                  d'      xs d*d*k7  rd	}#|&d/k(  r|"j	                  d'      xs d*d*k7  rd	}#|&d0k(  rt        |"j	                  d'            rd	}#|&d1k(  r|"j	                  d'      d2v rd	}#|&d3k(  st        |"j	                  d'            s8d	}#< | t<        k(  rd	}#t        || d	d	|||D cg c]
  }d4| d|  c}xs |g|d||      }t        ||||
      }d}'|
rt        |t              rt?        ||||      }'| ||dd|'|#g d5S tA        |||      r8t        |tB        d	d	||d6g|d||      }t        ||||
      }tB        ||dddg dS d}'|
rt        |t              rt?        ||||      }'t        |tD        d	d	||d7g|d	||      }tG        |      }(tI        |(      })|)r;d|d8<   t:        |d9<   |d:   d;|) gz   |d:<   t        ||||
      }t:        ||dd|'d	g d5S g }*|TtJ        |xs
 tM               |tD        d<d<d<d	dg dd<d=d>}+t        ||||
      },d}-|
rtO        |||+|      }-tD        ||,|-|+|'dg d5S 	  ||(tP              }.t[        |.dDdE      }0t[        |.dFd<      xs d<}1t[        |.dGd<      xs d<}2d@j                  |(      t]        |0      |xs
 tM               |1dHd |2dHd dI}3|*j_                  |3       |0d*k7  rd|d8<   tT        |d9<   |d:   dJ|0 dK|2ja                         ddL  gz   |d:<   t        ||||
      },tJ        |xs
 tM               |tT        d<d<d<d	d|*dd<dM}+d}-|
rtO        |||+|      }-tT        ||,|-|+|'d	|*d5S t        ||||
      },tJ        |xs
 tM               |tD        t[        |.dNd<      xs d<t[        |.dOd<      xs d<t[        |.dPd<      xs d<d	d|*dd<dM}+d}-|
rtO        |||+|      }-tD        ||,|-|+|'d|*d5S c c}w c c}w c c}w # tR        $ r}/d|d8<   tT        |d9<   |d:   d?|/gz   |d:<   t        ||||
      },tJ        |xs
 tM               |tT        d<d<d<d	dd@j                  |(      dA|xs
 tM               dBgdd<tW        |/      jX                   d|/ dC}+d}-|
rtO        |||+|      }-tT        ||,|-|+|'d	d@j                  |(      dA|xs
 tM               dBgd5cY d}/~/S d}/~/ww xY w)Qu  Decide and (when fully authorized) execute a real PR merge.

    Defaults are fail-closed:
      * ``activation_flag=False`` (spec §6.3 hardcoded default).
      * ``chair_authorization=None`` → NO_OP_NO_AUTHORIZATION.
      * ``subprocess_runner=None`` → never invokes ``gh pr merge`` even when
        every gate passes (regression contract — task md "subprocess injection
        mock").

    The function ALWAYS returns a dict with ``result_enum`` ∈ NO_OP_* /
    REAL_MERGE_* + ``decision`` payload + ``execution_result`` (when merge
    fired) + ``merge_decision_path`` / ``execution_result_path`` (when artifact
    writers ran).
    rF   rG   rg   Nexecutor_action)forbidden_pathsforbidden_dir_prefixespr_match	sha_matchTu6   chair_authorization pr/head_sha mismatch — pr_match=z sha_match=F)
rb   r`   ra   rf   rg   rc   r]   r\   rX   rY   )rl   rm   )rb   rk   merge_decision_pathexecution_result_pathexecution_resultchair_report_requiredsubprocess_invocationsclassificationunauthorized_forbidden_hitsauthorized_forbidden_hitszforbidden path hit: snapshot_presentproduction_in_snapshotz)snapshot contains production-area paths: z, blocking_secret_in_snapshotz/snapshot contains blocking-secret token paths: u2   activation_flag=False — default OFF (spec §6.3))ry   zchair_authorization z: zmerge_ready_result.verdict=u	    ≠ PASSz dryrun_artifact.executor_action=u    ≠ r[   zdryrun_artifact.schema=gatesnameadmin_override_requiredvalue>   critical7_hitsforbidden_pathblocking_secretpost_merge_smokedep_cycle_or_serialreplacement_pr_failexpected_files_exactr   r   r   net_new_identifier_exposure)r   Nr   r   r   r   r   )FFAILr   zgate_snapshot )rb   rk   r   r   r   snapshot_pathr   r   zCmerge_execution_result.json already present for this (pr, head_sha)z>all gates pass + chair_authorization OK + activation_flag=Truer\   rb   rc   z-INTERNAL: admin-override token leak in argv: rH   u@   subprocess_runner=None — inert pass path (no gh pr merge call))r[   r]   rQ   rb   merge_commit_shamergedAtmergedBy	non_adminadmin_override_usedr   post_smoke_okcallback_handoff_event_idnotezsubprocess raised:  )cmdexitts)r[   r]   rQ   rb   r   r   r   r   r   r   r   r   error
returncode   stdoutstderri8)r   r   r   stdout_tailstderr_tailzsubprocess exit=z stderr=   )r[   r]   rQ   rb   r   r   r   r   r   r   r   r   r   r   r   )1r   rW   r/   rL   rM   r   re   r1   r3   ri   r%   rd   rn   r$   r&   joinr'   r   r   r   r   r   r   r   r    r   r   r   r"   r!   r(   r   r   r#   r)   rw   rs   r   rD   r   _SUBPROCESS_TIMEOUT_SECONDS	Exceptionr*   type__name__getattrrN   r2   strip)4merge_ready_resultrQ   gate_snapshotdryrun_artifactcallback_envelopechair_authorizationr`   rx   rl   r5   rm   r]   ry   pidrF   rG   rg   rf   crossrefrk   r7   r   r   prX   auth_ok	auth_codeauth_reasonsrgate_ok	gate_codegate_reasonsrb   admin_override_hitgatechair_report
snap_gates_C7_GATE_NAMESr   r   rq   leaksr   r   decision_pathexecution_pathprocexcrcr   r   
invocations4                                                       r9   real_merge_executer     s   > 
{
+C	TB:H3=>PRV3W $$Y/]aG2<_d2S-.Y]  *_-#$:;H &+!6"+ 1(,'$Z01Xk=R<SU #&
  !./
 / #'%) $%)&(
 	
 #++;"<=Z"[ ()9 :;V W"", 1(,-@-L(M'9TUA+A3/U#&
 !./
 0 #'%) $%)&(
 	
 "#,-&A $_ 5,0+?ii)A BCD "'!"*H  %XnoD  E$'+)-$()-*,  12&F $_ 5,0+Eii)F GHI "'!"*H  %XnoD  J$'+)-$()-*,  +D& 
  "+!(,-@-L(M'IJ#%&
 !h~_no. #'%) $%*&(
 	
 (DS($GY ". (,-@-L(M'FRS+I;b<SbXaWb#%&
 !h~_no1 #'%) $%*&(
 	
 $"& (,'27+YGH#%&
 !h~_no) #'%) $%*&(
 	
 **"- (,'2=2C5I[H^_ #%&
 !h~_no0 #'%) $%*&(
 	
 /4(_-@-@-JNi-i"- (,')/*=*=h*G)J%.13 #%&
  !h~_no0 #'%) $%*&(
 	
 (>U($GY .7:.E*?
 #mT*%))'26<" tT*(,EETXXg./)-& ?*"+,@K *4]D*IMg&r 		
 j$'" (!$-xx'~-44dhhw>O9P#'L..HHW%*xx =>iO#'L338IU8R#'L--488G3D3Ia2O#'L--488G3D3Ia2O#'L22tDHHW<M7N#'L//DHHW4E4X#'L22tDHHW<M7N#'L1(4 @@L"# (,'@LM1~i[1#6M\R[Q\#%&
 !h~_no'+z->9Hm^M ' #'%) $*%1&(	
 		
  Hn="' (,'Z[#%&
 !h~_no* #'%) $%*&(
 	
 M:mT:5b(MSab#$(#QR!"H r"D%d+E(-$%"1&y1;E7C5
 
 !h~_no* #'%) $*%)&(	
 		
 46 
 .*
* "#(&(!)+V
 *#xhwx9"hHXZhiN* #0%3 0*%*&(	
 		
&
 'BCN 
|Q	'BT8R(.BFT8R(.BFxx~B"
de}de}J !!*-	Qw(-$%"3&y1rd(6<<>$3+?*@A5
 
 *#xhwx-*
, "#(&<!)+
 9"hHXZhiN, #0%3 0*%)&<	
 		
 &c8NdstM)&HJ&#D*<bAGRD*b17RD*b17R$"8%' N5b(DTVde&,!/,&!&"8	 	m Vz Td N@  $
(-$%"3&y17J3'5R4SS)#xhwx-*
, "#(&:NHJO' ")+S	**+2cU3
" 9"hHXZhiN, #0%3 0*%)&:NHJO'
 	
3$
s1   "d7d<%e/e 	h"Chh"h")r`   rx   rl   r5   rm   
env_lookupry   c               J    |t        |      }t        | |||||||||	|
|      S )uV  Single entry-point invoked from dispatch/__init__.py (회장 결정 #8).

    Resolves the activation flag (env var) when not pinned by the caller and
    delegates to real_merge_execute. This indirection lets dispatch keep its
    import minimal (1 line) while the switch itself stays here, per the
    finalize_hooks-minimal directive.
    )r   rQ   r   r   r   r   r`   rx   rl   r5   rm   ry   )r   r   )classifier_resultr   rQ   r   r   r   r`   rx   rl   r5   rm   r   ry   s                r9   dryrun_to_real_switchr     sE    . 1*=,#'+/'+%#' r;   )REAL_MERGE_HOOKS_SCHEMAr   r   r   r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   NO_OP_RESULTSr1   r3   r:   r   r   )r5   Optional[List[str]]return	List[str])r   r0   )rQ   r   r   Dict[str, Any])rQ   r   rb   r0   r`   rd   ra   rd   rf   Optional[str]rg   r   rc   r   r]   r   r\   rd   rX   r   rY   Optional[Dict[str, Any]]r   r   )
rQ   r   rk   r   rl   r   rm   rd   r   r   )rq   r   r   r   )rF   r   r   r   )r   r   rQ   r   r   r   r   r   r   r   r   r   r`   rd   rx   )Optional[Callable[[List[str], int], Any]]rl   r   r5   r   rm   rd   r]   r   ry   Optional[Any]r   r   )r   r   r   r   rQ   r   r   r   r   r   r   r   r`   zOptional[bool]rx   r   rl   r   r5   r   rm   rd   r   z(Optional[Callable[[str], Optional[str]]]ry   r   r   r   )D__doc__
__future__r   r   r   r   typingr   r   r	   r
   r   utils.activation_flag_validatorr   r   r   #utils.chair_authorization_validatorr   utils.gate_snapshot_validatorr   r   !utils.merge_ready_dryrun_executorr   r   utils.merge_ready_statesr    utils.real_merge_artifact_schemar   r   r   r   r   r   !utils.snapshot_crossref_validatorr   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   	frozensetr   r1   r3   rp   r   r:   rD   rW   ri   rn   rs   rw   r   r   __all__rC   r;   r9   <module>r      s=  < # 2 2 6 6 
 *  6 
 , 1 !/ #- #- + (O %-Y *)Q &#' 3 ).* $  "9i"89  ! 8< #'26$$ $ 	$
 "&$ !$ $ $ $ $  $ 0$ $NHHH "	H
 H H A, 4CG$()-  E
0E
E
 E
 	E

 E
 E
 E
 AE
 "E
 'E
 E
 E
 E
 E
` '+CG$()- ;?&%&#&  & 	&
 & & $& A& "& '& & 9& & &Tr;   