
    4j.                       d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	  ed      Z
e
dz  Ze
dz  Ze
d	z  Ze
d
z  ZdZdZdZdZefddZddZ	 	 	 	 ddZ	 	 	 	 	 	 	 	 ddZddZ	 	 	 	 	 	 ddZdddZedk(  r e e             y)u  run_test_only_hardening_pr_merge.py — profile-driven PR merge lifecycle runner.

task-2553+16 / Track 1. Profile-as-code orchestrator binding:

    profile load -> merge-readiness gate -> (BOT merge, head-pinned)
                  -> post-merge smoke -> reconcile -> closeout

Fail-closed by construction. The runner NEVER calls the GitHub merge endpoint
unless the merge_ready_predicate evaluates ALL_PASS *and* the runtime auth
invariant (9-R.5) holds *and* the caller passes --execute. Gate-only is the
default mode and is side-effect free.

9-R bindings enforced here:
  9-R.1  Gemini-thread resolve hard-bound: exactly-1 unresolved + unique
         blocking id + sole-remaining-blocker, else resolve 0 / PRE_MERGE_HOLD.
  9-R.2  review-state fail-closed allowlist {APPROVED, ""}; CLEAN is the only
         affirmative-safe mergeStateStatus.
  9-R.3  liveness budget; on exceed -> PRE_MERGE_HOLD (no spin).
  9-R.4  emits pre-merge-gate.json (pre-merge decision) + result.json (final).
  9-R.5  runtime auth invariant: BOT_GITHUB_TOKEN ghs_ App token only.
  9-R.6  deterministic merge_method: squash > merge ; rebase-only -> HOLD.

stdlib only.
    )annotationsN)Path)AnyOptionalz/home/jay/workspacezCschemas/policy_profiles/test_only_hardening_pr_merge_v1.schema.jsonz;memory/policy_profiles/test_only_hardening_pr_merge_v1.jsonz.memory/events/task-2553+16.pre-merge-gate.jsonz&memory/events/task-2553+16.result.jsonPRE_MERGE_HOLDPOST_MERGE_HOLDMERGE_READYMERGEDc                    | j                         st        d|        t        j                  | j	                  d            }|j                  d      dk7  rt        d      dD ]  }||vst        d|        |S )	z=Load the profile JSON. Fail-closed: missing/invalid -> raise.zprofile missing: utf-8encoding
profile_idtest_only_hardening_pr_merge_v1z!profile_id mismatch (fail-closed))merge_ready_predicatehold_conditionsmerge_methodauthgemini_thread_resolveliveness_budgetirreversibility_policyscope_invariantsz#profile missing mandatory section: )existsFileNotFoundErrorjsonloads	read_textget
ValueError)profile_pathdatakeys      ?/home/jay/workspace/scripts/run_test_only_hardening_pr_merge.pyload_profiler$   3   s     "3L> BCC::l,,g,>?Dxx!BB<==> J d?B3%HII	J
 K    c                p    |d   d   }| sy| j                  d      ry| j                  |      sdd| dfS y	)
zTrue only if token is the pre-configured taskctl-bot App token.

    OWNER PAT / personal PAT (ghp_/gho_/pat) / new token / absent / empty
    -> fail-closed (False).
    r   token_prefix_expected)Fauth_absent_or_empty)ghp_gho_github_pat_)Fowner_or_personal_pat_detectedFz!unexpected_token_prefix(expected ))Tgithub_app_installation_token)
startswith)tokenprofileexpected_prefixs      r#   auth_okr3   G   sR     fo&=>O,786O,9/9J!LLL0r%   c                r   |d   d   }t        | j                  d            t        | j                  d            t        | j                  d            |d}| j                  d      r	d|d<   d|fS | j                  d      r	d|d<   d|fS | j                  d      rd|d<   d	|d
<   d|fS d|d<   d|d
<   d|fS )z,squash > merge ; rebase-only -> HOLD (None).r   resolution_prioritysquashmergerebase)repo_allow_squash_mergerepo_allow_merge_commitrepo_allow_rebase_merger5   selectedNrebase_only_forbiddenhold_reasonno_merge_method_available)boolr   )
repo_allowr1   prio
provenances       r#   resolve_merge_methodrD   \   s     >"#89D#'
x(@#A#'
w(?#@#'
x(@#A#	J ~~h!)
:##~~g!(
:
""~~h!%
:$;
=!Z!Jz ;J}r%   c                :    |d   }| |d   k7  rdd|  dfS |sy|ryy)	zResolve permitted only when: unresolved == exactly 1, the single
    blocking thread is uniquely identified, it is the sole remaining
    blocker. Otherwise resolve 0.
    r   require_exact_unresolved_countFzunresolved_count=_not_exactly_1)F'blocking_thread_not_uniquely_identified)Fother_blockers_present)Teligible_single_resolve )unresolved_countunique_blocking_identifiedrI   r1   cfgs        r#   thread_resolve_eligiblerO   z   sC     )
*C3?@@)*:);>JJJ%?.*r%   c                   |d   }i }|d   | j                  d      | j                  d      |d   k(  d|d<   |d   | j                  d      | j                  d      |d   v d|d<   | j                  d	      }|d
v rdn|}|d   |||d   v d|d<   d| j                  d      | j                  d      du d|d<   d| j                  d      | j                  d      dk(  d|d<   d| j                  d      | j                  d      du d|d<   |d   d   | j                  d      | j                  d      |d   d   k(  d|d<   |d   d   | j                  d      | j                  d      |d   d   k(  d|d<   t        d |j                         D              }|j                         D cg c]  \  }}|d   r| }	}}|||	dS c c}}w )a  Evaluate the AND-gate predicate against an observation dict.

    obs keys: mergeable, merge_state_status, review_decision, ci_all_success,
              unresolved_review_threads, effective_diff_test_only,
              production_byte0_sha256, head_sha
    r   mergeable_eq	mergeable)expectedmeasuredpassmerge_state_status_inmerge_state_status)expected_inrT   rU   merge_state_status_cleanreview_decision)N r[   review_decision_allowlist)	allowlistrT   rU   Tci_all_successr   unresolved_review_threadsunresolved_review_threads_eq_0effective_diff_test_onlyproduction_byte0baseline_sha256production_byte0_sha256)expected_sha256rT   rU   targetsanctioned_head_shahead_shahead_sha_eq_sanctionedc              3  &   K   | ]	  }|d      yw)rU   NrK   ).0cs     r#   	<genexpr>z%evaluate_predicate.<locals>.<genexpr>   s     61V96s   rU   )checksALL_PASSfailed)r   allvaluesitems)
obsr1   prn   rdrd_normall_passkrl   rp   s
             r#   evaluate_predicaterz      s=    	'(A(*F n%GGK($.(99F> 01GG01,-3J1KK*F%&
 
"	#B*$b"G231899+F&' GG,-()T1 F GG783490F+, GG6723t;*F%& /01BCGG5612a8J6KL]6^^"F H%&;<GGJ'
#wx'89N'OO(F#$ 6fmmo66H"LLN<DAq!F)a<F<(fEE =s   ,G:Gc           	     <   t        | |      }t        ||      \  }}t        ||      \  }}|d   rt        nt        }	g }
|d   s&|
j                  ddj                  |d         z          |st        }	|
j                  d|z          |*t        }	|
j                  d|j                  dd	      z          | j                  d
      }d}|dvrEt        || j                  dd      | j                  dd      |      \  }}|
j                  d|        |	|d   |||d||||
dS )zHFull pre-merge decision. Returns a gate-decision dict (no side effects).ro   zpredicate_fail:,rp   zauth_fail_closed:Nzmerge_method:r>   
unresolvedr_   )r   NrM   FrI   Tzthread_resolve:)okreason)decisionro   	predicater   merge_method_provenancemerge_method_selectedrO   reasons)	rz   r3   rD   r	   r   appendjoinr   rO   )rt   r1   r0   rA   predok_authauth_reasonmethodmethod_provr   r   urcresolve_eligresolve_reasons                 r#   decider      sA    c7+D"5'2G[.z7CFK":.{NHG
(388DN+CCD!*[89~!)UUV ''-
.CL
)'>GG0%8GG,d3	(
$n 	(89: $+6#.!'#/	 	r%   c                
   t        j                         }|j                  dd       |j                  dd       |j                  ddd	
       |j                  dt        t                     |j                  |       }dd l}t               }|j                  s&t        t        j                  dt        d             yt        j                  t        |j                        j                  d            }|j                   j#                  |j$                        }|j#                  di       }t'        ||||      }t        |j(                        j+                  t        j                  |dd      d       |d   t,        k7  r(t        t        j                  |d   |d   d             y|j.                  s&t        t        j                  t,        dd             yt        t        j                  t,        dd             y)Nz--observationzpath to observation JSON)helpz--token-envBOT_GITHUB_TOKEN)defaultz	--execute
store_truez@permit BOT merge if and only if MERGE_READY (default: gate-only))actionr   z--emit-gater   no_observation_supplied)errorr      r   r   rA   F)indentensure_asciir   r   )r   r   zgate-only; --execute withheld)r   notez:execute requested; defer to orchestrator head-pinned merge)argparseArgumentParseradd_argumentstr	GATE_PATH
parse_argsosr$   observationprintr   dumpsr   r   r   r   environr   	token_envr   	emit_gate
write_textr	   execute)	argvapargsr   r1   rt   r0   rA   gates	            r#   mainr      s   		 	 	"BOOO*DOEOOM+=O>OOK[  ]OOM3y>O:==DnGdjj#<.YZ[
**T$**+55w5G
HCJJNN4>>*Er*J#wz2D##

46 $ J J;&djjd:&64	?STU<<djjk;Z[\] 
$**+7st
uvr%   __main__)r    r   returndict[str, Any])r0   Optional[str]r1   r   r   tuple[bool, str])rA   dict[str, bool]r1   r   r   z$tuple[Optional[str], dict[str, Any]])
rL   intrM   r@   rI   r@   r1   r   r   r   )rt   r   r1   r   r   r   )
rt   r   r1   r   r0   r   rA   r   r   r   )N)r   zOptional[list[str]]r   r   )__doc__
__future__r   r   r   pathlibr   typingr   r   	WORKSPACESCHEMA_PATHPROFILE_PATHr   RESULT_PATHr   r   r	   r
   r$   r3   rD   rO   rz   r   r   __name__
SystemExitrK   r%   r#   <module>r      s   2 #     &'	__XXHH	BB!#	 '3 (1*"05Y<+8<+48+ &4+ 9I+.7Ft((-<(AO(`%P z
TV
 r%   