
    q(j}              
      <   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Z!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ, e-e"e#e$e%e&e'e(e)h      Z.dZ/dZ0 e-ddh      Z1dZ2d*dZ3d+dZ4d,dZ5	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d-dZ6	 	 	 	 	 	 	 	 	 	 d.d Z7d/d!Z8d0d"Z9ed#d#d#d$d#d#d%	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d1d&Z:d#d#d#d#d$d#d#d'	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d2d(Z;g d)Z<y#)3u+  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_snapshotzutils.real_merge_hooks.v1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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       G/home/jay/workspace/.worktrees/task-2638-dev6/utils/real_merge_hooks.pydetect_forbidden_pathsr4   {   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 r5   r3   _now_kstr>      s6    X\\"	HYQ/0	1	+	,r5   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)r@   rA   rC   rD   rE   )r)   dictgetint	TypeError
ValueErrorr*   )pr_identitypiraw_prsafe_prraw_head	safe_heads         r3   _identity_or_defaultrQ      s    ";52BVVD\F!'!3#f+ vvj"%H&x52I66)R(&&2&FF:r* 	 z" s   B B('B(c                   t         ||xs
 t               | |xs d|xs dt        |      t        |      |t        |      d
S )NrB   )
schemaactually_executedts_kstrK   verdict_inputdryrun_action_inputactivation_flagchair_authorization_presentresult_enumreasons)r   r>   boollist)	rK   rZ   rX   rY   dryrun_actionverdictr[   rU   rT   s	            r3   _build_decisionr`      sK     (.&HJ" B,20'+,G'H"= r5   c               r    |sy | j                  d      }| j                  d      }||sy t        ||||      S )Nr@   rA   )rG   r   )rK   decisioncanonical_rootwrite_artifactsr@   rA   s         r3   _persist_decisionre      sA     		Bz*H	zHhGGr5   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     r3   _validate_runner_no_adminrj      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.
    ghr@   mergez--mergez--delete-branch=false)r*   rH   )r@   s    r3   _compose_merge_argvrn      s%     	dGSR\ r5   NT)rX   subprocess_runnerrc   r/   rd   rU   clockc               h   t                t        |      }|d   }|d   }t        | t              r| j	                  d      nd}t        |t              r|j	                  d      nd}t        |	      }|rZt        |t        t        |      t        |du      |||D cg c]  }d| 	 c}|d	      }t        ||||
	      }t        ||ddd
g dS t        |      sAt        |t        dt        |du      ||dg|d	      }t        ||||
	      }t        ||dddg dS t        |||      \  }}}|sYt        |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  r@t        |t         d
d
||d|dt        g|d	      }t        ||||
	      }t         ||dddg dS t        |t              rg|j	                  d      t"        k7  rOt        |t         d
d
||d|j	                  d      dt"        g|d	      }t        ||||
	      }t         ||dddg dS t%        ||      \  }}}|s|t&        k(  rt(        n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
}|"d"k(  rt        |!j	                  d            rd
}|"d#k(  r|!j	                  d      d$v rd
}|"d%k(  st        |!j	                  d            s8d
}< t        ||d
d
|||D cg c]
  }d&| d|  c}xs |g|d	      }t        ||||
	      }d}#|
rt        |t              rt/        ||||      }#|||dd|#|g d'S t1        |||      r6t        |t2        d
d
||d(g|d	      }t        ||||
	      }t2        ||dddg dS d}#|
rt        |t              rt/        ||||      }#t        |t4        d
d
||d)g|d
	      }t7        |      }$t9        |$      }%|%r;d|d*<   t*        |d+<   |d,   d-|% gz   |d,<   t        ||||
	      }t*        ||dd|#d
g d'S g }&|Tt:        |xs
 t=               |t4        d.d.d.d
dg dd.d/d0}'t        ||||
	      }(d})|
rt?        |||'|      })t4        ||(|)|'|#dg d'S 	  ||$t@              }*tM        |*d6d7      },tM        |*d8d.      xs d.}-tM        |*d9d.      xs d.}.d2jG                  |$      tO        |,      |xs
 t=               |-d:d |.d:d d;}/|&jQ                  |/       |,dk7  rd|d*<   tD        |d+<   |d,   d<|, d=|.jS                         dd>  gz   |d,<   t        ||||
	      }(t:        |xs
 t=               |tD        d.d.d.d
d|&dd.d?}'d})|
rt?        |||'|      })tD        ||(|)|'|#d
|&d'S t        ||||
	      }(t:        |xs
 t=               |t4        tM        |*d@d.      xs d.tM        |*dAd.      xs d.tM        |*dBd.      xs d.d
d|&dd.d?}'d})|
rt?        |||'|      })t4        ||(|)|'|#d|&d'S c c}w c c}w c c}w # tB        $ r}+d|d*<   tD        |d+<   |d,   d1|+gz   |d,<   t        ||||
	      }(t:        |xs
 t=               |tD        d.d.d.d
dd2jG                  |$      d3|xs
 t=               d4gdd.tI        |+      jJ                   d|+ d5}'d})|
rt?        |||'|      })tD        ||(|)|'|#d
d2jG                  |$      d3|xs
 t=               d4gd'cY d}+~+S d}+~+ww xY w)Cu  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).
    r@   rA   r_   Nexecutor_actionzforbidden path hit: F)rZ   rX   rY   r^   r_   r[   rU   rT   )rc   rd   T)rZ   rb   merge_decision_pathexecution_result_pathexecution_resultchair_report_requiredsubprocess_invocationsu2   activation_flag=False — default OFF (spec §6.3))rp   zchair_authorization z: zmerge_ready_result.verdict=u	    ≠ PASSz dryrun_artifact.executor_action=u    ≠ rS   zdryrun_artifact.schema=gates>   critical7_hitsforbidden_pathblocking_secretpost_merge_smokedep_cycle_or_serialreplacement_pr_failexpected_files_exactadmin_override_requirednamer   valuer{   r   net_new_identifier_exposure)r   Nr   rz   ry   r~   r|   )FFAILr}   zgate_snapshot )rZ   rb   rs   rt   ru   snapshot_pathrv   rw   zCmerge_execution_result.json already present for this (pr, head_sha)z>all gates pass + chair_authorization OK + activation_flag=TruerT   rZ   r[   z-INTERNAL: admin-override token leak in argv: rB   u@   subprocess_runner=None — inert pass path (no gh pr merge call))rS   rU   rK   rZ   merge_commit_shamergedAtmergedBy	non_adminadmin_override_usedrw   post_smoke_okcallback_handoff_event_idnotezsubprocess raised:  )cmdexitts)rS   rU   rK   rZ   r   r   r   r   r   rw   r   r   error
returncode   stdoutstderri8)r   r   r   stdout_tailstderr_tailzsubprocess exit=z stderr=   )rS   rU   rK   rZ   r   r   r   r   r   rw   r   r   r   r   r   )*r   rQ   r)   rF   rG   r4   r`   r"   r\   re   r   r   r   r   r   r   r   r   r   r   r    r   r]   r   r   r!   r#   rn   rj   r   r>   r   _SUBPROCESS_TIMEOUT_SECONDS	Exceptionr$   jointype__name__getattrrH   r,   strip)0merge_ready_resultrK   gate_snapshotdryrun_artifactcallback_envelopechair_authorizationrX   ro   rc   r/   rd   rU   rp   pidr@   rA   r_   r^   forbidden_hitsprb   r1   auth_ok	auth_codeauth_reasonsrgate_ok	gate_codegate_reasonsrZ   chair_report
snap_gates_C7_GATE_NAMESgater   r   rh   leaksrw   ru   decision_pathexecution_pathprocexcrcr   r   
invocations0                                                   r3   real_merge_executer      s
   > 
{
+C	TB:H3=>PRV3W $$Y/]aG2<_d2S-.Y] 
 ,M:N", 1(,-@-L(M'9GHA+A3/H#

 !h~_no/ #'%) $%)&(
 	
  "+!(,-@-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*? *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(2 ## (,'@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	 	O If Tf Nt  $
(-$%"3&y17J3'5R4SS)#xhwx-*
, "#(&:NHJO' ")+S	**+2cU3
" 9"hHXZhiN, #0%3 0*%)&:NHJO'
 	
3$
s1   ]]:]>] 	`1C`,&`1,`1)rX   ro   rc   r/   rd   
env_lookuprp   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   rK   r   r   r   r   rX   ro   rc   r/   rd   rp   )r   r   )classifier_resultr   rK   r   r   r   rX   ro   rc   r/   rd   r   rp   s                r3   dryrun_to_real_switchr     sE    . 1*=,#'+/'+%#' r5   )REAL_MERGE_HOOKS_SCHEMAr   r   r   r   r   r    r!   r"   r#   r$   r%   NO_OP_RESULTSr+   r-   r4   r   r   )r/   Optional[List[str]]return	List[str])r   r*   )rK   r   r   Dict[str, Any])rK   r   rZ   r*   rX   r\   rY   r\   r^   Optional[str]r_   r   r[   r   rU   r   rT   r\   r   r   )
rK   r   rb   r   rc   r   rd   r\   r   r   )rh   r   r   r   )r@   r   r   r   )r   zOptional[Dict[str, Any]]rK   r   r   r   r   r   r   r   r   r   rX   r\   ro   )Optional[Callable[[List[str], int], Any]]rc   r   r/   r   rd   r\   rU   r   rp   Optional[Any]r   r   )r   r   r   r   rK   r   r   r   r   r   r   r   rX   zOptional[bool]ro   r   rc   r   r/   r   rd   r\   r   z(Optional[Callable[[str], Optional[str]]]rp   r   r   r   )=__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   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   	frozensetr   r+   r-   rg   r   r4   r>   rQ   r`   re   rj   rn   r   r   __all__r=   r5   r3   <module>r      s  < # 2 2 6 6 
 *  6 
 , 1 !/ #- #- #' 3 	 	  "9i"89  ! 8&  	
 "& !     4HHH "	H
 H H A, 4CG$()-  d0dd d 	d
 d d d Ad "d 'd d d d d^ '+CG$()- ;?&%&#&  & 	&
 & & $& A& "& '& & 9& & &Tr5   