
    9j;!                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ  ee      j                         j                  d   Z ee      e
j$                  vr"e
j$                  j'                  d ee             ddl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m Z  dd
l!m"Z" ddl#m$Z$m%Z% dZ&dZ'de&fdZ(ddddZ)d Z*d Z+d Z,y)u  task-2556 §11 / §4 — head 변경 후 FAILED (race condition fail-closed) 회귀.

회장 §명시 2026-05-12:
  §4 scheduler 가 trigger 호출 직전에 PR head 가 변경되면 race condition.
  §11 token boundary + decision schema 검증 — current_head_actual 가 decision.current_head 와
       불일치하면 DecisionInvalidError(E_HEAD_MISMATCH) → owner trigger FAILED.

검증 포인트:
  1. snapshot 진단 후 trigger 호출 직전에 head 가 바뀌면 owner_trigger 가 fail-closed.
  2. scheduler 는 OWNER_TRIGGER_FAILED marker 생성.
  3. http_post 호출 0.
  4. audit JSONL 에 POSTED 기록 없음.
  5. fixture (executor_head_mismatch.json) 결과 어셀션.
  6. decision.json 은 생성됨 (스냅샷 head 기준).
    )annotationsN)Path   )ACTION_OWNER_TRIGGER_FAILEDExecutorScheduler)IdlePRDiagnoserIdlePRSnapshot"STATE_FIRST_GEMINI_TRIGGER_MISSING)MergeQueueExecutor)AUDIT_REL_PATHOwnerTriggerAuditRESULT_POSTED)DecisionInvalidError)OwnerTriggerOnlyinvoke_from_scheduler(dddddddddddddddddddddddddddddddddddddddd(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeo   c                $    t        | |dddd      S )Nztask/task-2556-dev5z2026-05-12T10:00:00+00:00 Tnumberhead_shahead_ref
created_atgemini_reviewsci_required_all_success)r	   )r   heads     K/home/jay/workspace/anu_v2/tests/test_executor_head_mismatch_fail_closed.py	_snapshotr    6   s!    &. $     #ghp_owner_fake_xxxxxxxxxxxxxxxxxxxxtokenc               F    fd}t        | |fdt        |             S )Nc                4    j                  ||d       ddiS )N)pathbodyid   )append)methodr'   r(   headers
http_callss       r   	http_postz&_make_owner_trigger.<locals>.http_postB   s     467ayr!   c                      S Nr   r#   s   r   <lambda>z%_make_owner_trigger.<locals>.<lambda>H   s    u r!   workspace_rootr/   token_provideraudit)r   r   )tmp_pathr.   r$   r/   s    `` r   _make_owner_triggerr8   A   s(     $)	 r!   c                F   g }t        | |      }| dz  }|j                  dd       |dz  }|j                  t        j                  dddt
        ddd	d
ddddd      d       d
dl}|j                  t              5 }t        ||ddt               ddd       j                  }|j                  }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      t        j$                  |      t        j$                  |	      dz  }dd|iz  }t'        t        j(                  |            dx}x}x}
}	t+        |      }d
}
||
k(  }|st        j                  d|fd||
f      dt        j                          v st        j"                  t*              rt        j$                  t*              nddt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      t        j$                  |
      dz  }d d!|iz  }t'        t        j(                  |            dx}x}}
y# 1 sw Y   xY w)"uG   invoke_from_scheduler 가 head_mismatch 시 DecisionInvalidError raise.r.   eventsT)parentsexist_ok%task-2556.owner_trigger_decision.jsonz anu_v2.owner_trigger_decision.v1z	task-2556r   Fr   "POST_GEMINI_REVIEW_TRIGGER_COMMENTz/gemini review)schematask_idprcurrent_head
queue_headcurrent_head_confirmedgemini_evidence_freshnudge_count_for_pr_headallowed_actioncomment_bodyallowed)	sort_keysutf-8encodingNor)decision_pathownerrepocurrent_head_actualE_HEAD_MISMATCH==)zG%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.value
}.code
} == %(py7)sexcinfo)py0py2py4py7zassert %(py9)spy9z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr.   rY   py1py3py6assert %(py8)spy8)r8   mkdir
write_textjsondumps_HEAD_DECIDEDpytestraisesr   r   _HEAD_ACTUAL_DIFFERENTvaluecode
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr_   )r7   r.   runnerdecision_dirrQ   rk   rX   @py_assert1@py_assert3@py_assert6@py_assert5@py_format8@py_format10@py_assert2@py_assert4@py_format7@py_format9s                    r   @test_invoke_from_scheduler_head_mismatch_raises_decision_invalidr   M   s   J jAF h&Ltd3 #JJM

8")&*%*'(B,
 	   " 	+	, 
'C 6		

 ==2=2!22!22222!222222272227222=222222!22222222z?a?a?a33zz?a
 
s   >JJ c                ,   g }t        | |      } G d dt              } || |j                  |j                  |j                        }t        | | dz  dz  d |t        d d	 d
 d |       dd      }|j                  ddid      }|j                  d   }|j                  }|t        k(  }	|	st        j                  d|	fd|t        f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      dt        j                         v st        j                  t              rt        j                   t              nddz  }
dd|
iz  }t#        t        j$                  |            dx}}	|j&                  }|t(        k(  }	|	st        j                  d|	fd|t(        f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      dt        j                         v st        j                  t(              rt        j                   t(              nddz  }
dd|
iz  }t#        t        j$                  |            dx}}	| dz  dz  dz  }|j*                  } |       }	|	sd d!t        j                         v st        j                  |      rt        j                   |      nd!t        j                   |      t        j                   |	      dz  }
t#        t        j$                  |
            dx}}	t-        j.                  |j1                  d"#            }|d$   }|t2        k(  }|st        j                  d|fd%|t2        f      t        j                   |      d&t        j                         v st        j                  t2              rt        j                   t2              nd&d'z  }d(d)|iz  }t#        t        j$                  |            dx}}| dz  dz  d*z  }|j*                  } |       }	|	sd d+t        j                         v st        j                  |      rt        j                   |      nd+t        j                   |      t        j                   |	      dz  }
t#        t        j$                  |
            dx}}	t5        |      }d}||k(  }|st        j                  d|fd,||f      d-t        j                         v st        j                  t4              rt        j                   t4              nd-d.t        j                         v st        j                  |      rt        j                   |      nd.t        j                   |      t        j                   |      d/z  }d0d1|iz  }t#        t        j$                  |            dx}x}}| t6        z  }|j+                         r|j1                  d"#      j9                         D ]l  }|st-        j.                  |      }|j:                  }d2}	 ||	      }|t<        k7  }|s't        j                  d3|fd4|t<        f      d5t        j                         v st        j                  |      rt        j                   |      nd5t        j                   |      t        j                   |	      t        j                   |      d6t        j                         v st        j                  t<              rt        j                   t<              nd6d7z  }t        j>                  d8|       d9z   d:|iz  }t#        t        j$                  |            dx}x}	x}}o yy);u  scheduler 의 _dispatch_owner_trigger 가 DecisionInvalidError 를 catch 해
    OWNER_TRIGGER_FAILED marker 생성 후 SchedulerPRAction 반환.

    시나리오: emit_owner_trigger_decision 이 새 head=_HEAD_DECIDED 로 decision.json 생성.
    그러나 invoke_from_scheduler 의 current_head_actual 를 다른 값으로 강제하기 위해
    OwnerTriggerOnly 를 wrapping — 정상 path 에서는 mismatch 가 발생할 수 없지만
    race 조건 시뮬레이션을 위해 SnapshotProvider 가 한 head 를 반환하고 그 뒤 또 다른 head 가
    actual 로 들어가는 상황을 흉내내는 wrapper subclass 를 사용.
    r:   c                  *     e Zd ZdZddd fd
Z xZS )`test_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.HeadMutatingRunneru  trigger_gemini_review 호출 시 current_head_actual 를 강제로 변조해 mismatch 유발.

        실제 environment 에서는 scheduler 가 _dispatch_owner_trigger 에서 diag.head_sha 를
        current_head_actual 로 넘기므로 mismatch 가 발생하지 않지만, race 시뮬레이션을 위해
        wrapper 가 호출 직전에 _HEAD_ACTUAL_DIFFERENT 로 교체.
        N)env_overriderI   c               6    t         |   |||t        ||      S )N)rQ   rR   rS   rT   r   rI   )supertrigger_gemini_reviewrm   )selfrQ   rR   rS   rT   r   rI   	__class__s          r   r   zvtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.HeadMutatingRunner.trigger_gemini_review   s-     70+$$:)) 1  r!   )__name__
__module____qualname____doc__r   __classcell__)r   s   @r   HeadMutatingRunnerr      s    	 FJ04		 		r!   r   r3   memoryr;   c                 $    t        dt              gS )Nr   )r    rj   r   r!   r   r2   zVtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.<lambda>   s    9S-#@"A r!   c                    i S r1   r   )aes     r   r2   zVtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.<lambda>   s    2 r!   c                     y)N r   )r   s    r   r2   zVtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.<lambda>       r!   c                     y)Nr   r   ps    r   r2   zVtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.<lambda>   r   r!   c                     y r1   r   r   s    r   r2   zVtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalid.<locals>.<lambda>   r   r!   )	gh_runner
git_runnerpytest_runneraudit_writertask_md_rootrO   rP   )r4   ry   snapshot_providerowner_triggermerge_executorrR   rS   OWNER_GEMINI_TRIGGER_TOKENghp_xxxxxxxxxxxxxxxxxxxxxxxxz2026-05-12T11:00:00+00:00)envnowr   rV   )z.%(py2)s
{%(py2)s = %(py0)s.action
} == %(py4)sactionr   rY   rZ   r[   assert %(py6)src   Nz-%(py2)s
{%(py2)s = %(py0)s.state
} == %(py4)sr
   r>   zAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}rQ   rL   rM   rC   z%(py1)s == %(py3)srj   ra   rb   assert %(py5)spy5ztask-2556.owner-trigger.failedfailed_markerr^   r_   r.   r`   rd   re   result)!=)zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} != %(py8)srecr   )rY   rZ   r[   rc   re   z+audit unexpectedly contains POSTED record: z
>assert %(py10)spy10) r8   r   
_http_post_token_provider_auditr   r   run_one_cycle
pr_actionsr   r   rp   rq   rr   rs   rt   ru   rv   rw   stater
   existsrh   loads	read_textrj   r_   r   
splitlinesgetr   _format_assertmsg)r7   r.   
underlyingr   rx   	schedulerr   r   rz   r{   @py_format5r   rQ   decision@py_assert0r   @py_format4@py_format6r   r}   r   r   
audit_pathliner   @py_assert7@py_format11s                              r   Dtest_scheduler_dispatches_owner_trigger_failed_when_decision_invalidr   s   s0    J$X*EJ- &  ''!11	F "(83A)%#%'!
 I $$)+IJ' % F
 q!F==7=77777=777777767776777=777777777777777777<<=<=====<=======6===6===<================== x'(25\\M!!!!!!!!!=!!!=!!!!!!!!!!!!!zz-1171CDHN#4#}4444#}444#444444}444}4444444 x'(25UUM!!!!!!!!!=!!!=!!!!!!!!!!!!! z?a?a?a33zz?a N*J(('(:EEG 	D**T"C77 8 78$ $5   $  v     I   I   I $  I %  v   )6  I )6    >cUC     		 r!   c           	        t        t              j                         j                  d   dz  dz  }t	        j
                  |j                  d            }|d   }d}||k(  }|slt        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|d   }|t        k(  }|st        j                  d|fd|t        f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }	t        t        j                  |	            d x}}|d   }
t!        |
d   |
d   |
d   |
d   d|
d         }t#               j%                  |d      }|j&                  }|t(        k(  }|st        j                  d|fd|t(        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t(              rt        j                  t(              ndd z  }dd|iz  }t        t        j                  |            d x}}y )!Nr*   fixtureszexecutor_head_mismatch.jsonrL   rM   expected_owner_trigger_outcome DECISION_INVALID_E_HEAD_MISMATCHrV   )z%(py1)s == %(py4)s)ra   r[   r   rc   expected_scheduler_actionr   r   r   r   r   snapshotr   r   r   r   r   r   r   z2026-05-12T12:00:00+00:00)r   r   diagr
   r   )r   __file__resolver<   rh   r   r   rp   rq   ru   rv   rw   r   rr   rs   rt   r	   r   diagnoser   r
   )r7   fixture_pathfixturer   r{   r   r   r   r   r   	snap_dictsnapr   rz   s                 r   +test_head_mismatch_fixture_matches_expectedr      s   X ((+j8;XX  jj///ABG34Z8ZZ48ZZZZZ48ZZZZ4ZZZ8ZZZZZZZZ./N/3NNNNN/3NNNN/NNNNNN3NNNN3NNNNNNNN 
#I":&:&\* )*C DD %%d0K%LD::;:;;;;;:;;;;;;;4;;;4;;;:;;;;;;;;;;;;;;;;;;r!   )r7   r   r.   list)-r   
__future__r   builtinsrr   _pytest.assertion.rewrite	assertionrewriterp   rh   syspathlibr   r   r   r<   WORKSPACE_ROOTstrr'   insertanu_v2.executor_schedulerr   r   anu_v2.idle_pr_diagnoserr   r	   r
   anu_v2.merge_queue_executorr   anu_v2.owner_trigger_auditr   r   r   anu_v2.owner_trigger_decisionr   anu_v2.owner_trigger_onlyr   r   rj   rm   r    r8   r   r   r   r   r!   r   <module>r      s     #    
  h'')11!4~chh&HHOOAs>*+ 
 ; 
 ? !  }  Di 	# LTn<r!   