
     jZ                    d   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ZddlZddlmZ ddlmZ ddlmZmZ  ee      j+                         j,                  d   Z ee      ej2                  vr"ej2                  j5                  d ee             ddlmZmZmZmZm Z   ee      jB                  jB                  d	z  Z"dd
Z#ddddded	 	 	 ddZ$ddZ%ddZ&ddZ'd dZ(d!dZ)d"d#dZ*d$dZ+d$dZ,d$dZ-d$dZ.d$dZ/ ed      d$d       Z0 ed      d$d       Z1 ed      d$d       Z2d$dZ3y)%u4  anu_v2.tests.test_pr_open_gemini_trigger_prevention_2544 — 회귀 9건 (task-2544).

회귀 케이스 (회장 §명시):
  1. preflight_base_head_fresh             — behind=0, diverged=False → fresh=True
  2. preflight_ref_fetchability_ok         — fetch exit=0, merge-base exit=0 → fetchable=True
  3. preflight_git_exit_128_detection      — fetch exit=128 → fetchable=False, git_exit_code=128
  4. pr_open_head_ref_oid_mismatch         — headRefOid != pushed_sha → match=False, INTERNAL_HEAD_MISMATCH
  5. gemini_review_gate_check_missing      — check-runs에 gemini-review-gate 없음 + git_exit_128=True
  6. first_evidence_grace_window_expiry    — poller 항상 [], clock으로 180초 만료 → call_count ≤ 6
  7. pr86_fixture_external_trigger         — fixture 기반 run() → EXTERNAL_TRIGGER_REQUIRED
  8. normal_pr_open_classification_ok      — 모든 정상 → PR_OPEN_GEMINI_TRIGGER_OK
  9. post_merge_audit_warn_to_pass_spec    — spec 정합 + isolation 검증

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path)Any)Mockpatch   )(CLASSIFICATION_EXTERNAL_TRIGGER_REQUIRED%CLASSIFICATION_INTERNAL_HEAD_MISMATCHCLASSIFICATION_OKDEFAULT_CHAT_IDPROpenGeminiTriggerPreventionfixturesc                    | j                  d      s|  d} t        j                  t        | z  j	                  d            S )u   Local helper for fixture JSON loading. Test 파일 내부 한정.

    회장 §명시 (2026-05-10): anu_v2/fixtures/__init__.py 외부에 두지 않음.
    Critical #3 사고 재발 방지를 위한 local 정의.z.jsonzutf-8)encoding)endswithjsonloads_FIXTURE_DIR	read_text)names    O/home/jay/workspace/anu_v2/tests/test_pr_open_gemini_trigger_prevention_2544.py_load_fixturer   *   s=    
 ==!u~::|d*55w5GHH    	gh_runner
git_runnerevidence_pollerclockaudit_writerchat_idc                $    t        | |||||      S )u?   테스트용 PROpenGeminiTriggerPrevention 인스턴스 생성.r   )r   r   s         r   _make_moduler"   6   s#     )'! r   c                     ddidfd} | S )u=   fresh 상태 git_runner stub: behind=0, diverged=0, fetch=OK.nr   c                n    dxx   dz  cc<   dj                  |       }d|v rdddS d|v rdd	dS dddS )
Nr$       fetchr    	exit_codestdoutrev-list0join)_argscmd
call_counts     r   r   z#_git_stub_fresh.<locals>.git_runnerN   sN    31hhuoc>!"b11!"c22"--r   r1   	list[str]returndict )r   r3   s    @r   _git_stub_freshr9   J   s    qJ. r   c                     dd} | S )z>fetchable OK git_runner stub: fetch exit=0, merge-base exit=0.c                d    dj                  |       }d|v rdddS d|v rdddS d|v rdd	dS dddS )
Nr'   r(   r   r)   r*   
merge-baseabc123r-   r.   r/   r1   r2   s     r   r   z*_git_stub_fetchable_ok.<locals>.git_runner\   sR    hhuoc>!"b113!"h77!"c22"--r   r4   r8   r   s    r   _git_stub_fetchable_okr@   Z   s    . r   c                     dd} | S )z6exit 128 git_runner stub: fetch exit=128 (broken ref).c                B    dj                  |       }d|v rddddS dddS )	Nr'   r(      r)   zfatal: unable to fetch)r+   r,   stderrr   r*   r/   r>   s     r   r   z&_git_stub_exit_128.<locals>.git_runnerk   s/    hhuoc>!$>VWW"--r   r4   r8   r?   s    r   _git_stub_exit_128rE   i   s    . r   c                     d fd}|S )z#headRefOid mismatch gh_runner stub.c                "    d | v rdiS dg iS )Npulls/
headRefOid
check_runsr8   )endpointr1   
actual_sha	pr_numbers     r   r   z,_gh_stub_pr_head_mismatch.<locals>.gh_runnerv   s(    I;8+ *--b!!r   rK   strr1   r5   r6   r7   r8   )rM   _expected_sharL   r   s   ` ` r   _gh_stub_pr_head_mismatchrQ   t   s    " r   c                     d fd}|S )u  gemini-review-gate 없는 check-runs + git_exit_128 시뮬레이션용 gh_runner.

    check-runs에 gemini-review-gate가 없고, exit 128 여부는 gate_present 내부 로직에서 결정.
    여기서는 failure + exit 128 텍스트가 들어있는 다른 check로 대신 커버.
    실제로는 check_present=False → git_exit_128은 별도로 preflight에서 옴.
    이 stub은 check-runs를 빈 배열로 반환해 check_present=False로 만든다.
    c                *    d| v rdddddidgiS diS )	N
check-runsrJ   zci-buildfailuretextzexit 128 fatal: r   
conclusionoutputrI   r8   )rK   r1   head_shas     r   r   z5_gh_stub_gate_missing_with_exit128.<locals>.gh_runner   s@    8#  *&/#)+=">  h''r   rN   r8   )rZ   r   s   ` r   "_gh_stub_gate_missing_with_exit128r[      s    ( r   c                     ddidfd}|S )uQ   clock stub: 매 호출마다 interval씩 증가. grace_seconds 초과 시 만료.t        c                 ,    d   } dxx   z  cc<   | S )Nr]   r8   )valintervalstates    r   r   z"_clock_advance_stub.<locals>.clock   s    Cjc
h

r   r6   floatr8   )_grace_secondsra   r   rb   s    ` @r   _clock_advance_stubrf      s    #JE
 Lr   c                 ~   t        t                     } | j                  dd      }|d   }d}||u }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d	|       d
z   d|iz  }t        t        j                  |            dx}x}}|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   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }g }||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u9   git_runner stub: behind=0, diverged=False → fresh=True.r?   mainzfeature/testfreshTisz%(py1)s is %(py4)spy1py4zExpected fresh=True, got 
>assert %(py6)spy6Nbehind_countr   ==z%(py1)s == %(py4)sassert %(py6)shead_divergedFreasons)	r"   r9   preflight_base_head_freshness
@pytest_ar_call_reprcompare	_saferepr_format_assertmsgAssertionError_format_explanationmodresult@py_assert0@py_assert3@py_assert2@py_format5@py_format7s          r   test_preflight_base_head_freshr      sx   
/"3
4C..v~FF'?HdH?d"HHH?dHHH?HHHdHHH&?x$HHHHHHHH.!&Q&!Q&&&&!Q&&&!&&&Q&&&&&&&/"+e+"e++++"e+++"+++e+++++++)"""""""""""""""""""r   c                 z   t        t                     } | j                  d      }|d   }d}||u }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|       d	z   d
|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}y)u`   git_runner stub: fetch exit=0, merge-base exit=0 → fetchable=True, merge_base_resolvable=True.r?   abc123deadbeef	fetchableTrj   rl   rm   zExpected fetchable=True, got rp   rq   Nmerge_base_resolvablerv   git_exit_code)	r"   r@   preflight_ref_fetchabilityrz   r{   r|   r}   r~   r   r   s          r   "test_preflight_ref_fetchability_okr      s+   
"8":
;C++,<=F+P$P$&PPP$PPPPPP$PPP*Gx(PPPPPPPP)*2d2*d2222*d222*222d2222222/"*d*"d****"d***"***d*******r   c                 |   t        t                     } | j                  d      }|d   }d}||u }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d|       d	z   d
|iz  }t        t        j                  |            dx}x}}|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   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}y)uT   git_runner stub: fetch exit=128 (broken ref) → fetchable=False, git_exit_code=128.r?   deadbeef0000r   Frj   rl   rm   zExpected fetchable=False, got rp   rq   Nr   rC   rs   ru   rv   r   )	r"   rE   r   rz   r{   r|   r}   r~   r   r   s          r   %test_preflight_git_exit_128_detectionr      s*   
"4"6
7C++N;F+R%R%'RRR%RRRRRR%RRR+I&)RRRRRRRR/")c)"c))))"c)))")))c))))))))*3e3*e3333*e333*333e3333333r   c                    d} d}d}t        || |      }t        |      }|j                  ||       }|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}}|d   }||k(  }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }dd|iz  }t        t        j                  |            dx}}|d   }|| k(  }|st        j                  d|fd|| f      t        j
                  |      dt        j                         v st        j                  |       rt        j
                  |       nddz  }dd|iz  }t        t        j                  |            dx}}ddddddd}dddg d}ddddd}|j                  ||||      }|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   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            dx}x}}|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}}y)#uc   gh_runner stub: pulls/{n} returns headRefOid != pushed_sha → match=False, INTERNAL_HEAD_MISMATCH.aaaabbbbcccc1111ddddeeee22223333c   r   matchFrj   rl   rm   rv   rq   Ngithub_head_shars   z%(py1)s == %(py3)s
github_sharn   py3assert %(py5)spy5
pushed_shaTr   ri   rr   rw   r   r   r   successcheck_presentgit_exit_128rX   internal_cause_candidate
   )evidence_arrivedelapsed_seconds	review_idreview_commit_idclassificationr
   auto_retry_allowed
human_onlynext_actionrepush_head_and_retryru   )rQ   r"   verify_pr_head_sha_matchrz   r{   r|   r~   r   @py_builtinslocals_should_repr_global_nameclassify_trigger_missr
   )r   r   rM   r   r   
head_matchr   r   r   r   r   @py_format4@py_format6	preflightgate_presentevidencer   s                    r   "test_pr_open_head_ref_oid_mismatchr      s   #J#JI))ZLI

+C--iDJg'%'%''''%''''''%''''''''(6(J6666(J666(666666J666J6666666l#1#z1111#z111#111111z111z1111111 E"TTXZI%)5PY024L$)btimnH..y*lT\]N*+T+/TTTTT+/TTTT+TTTTTT/TTTT/TTTTTTTT./747/47777/4777/77747777777,'050'50000'5000'00050000000-(C,CC(,CCCCC(,CCCC(CCC,CCCCCCCCr   c                    dt              } t        |       }|j                  d      }|d   }d}||u }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }t        j                  d
|       dz   d|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }dd|iz  }t        t        j                  |            dx}x}}|d   }t        |      }d}	||	k\  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  t              rt        j
                  t              ndt        j
                  |      t        j
                  |      t        j
                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}x}
}	dfd}t        |      }|j                  d      }|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }dd|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }dd|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      d	z  }dd|iz  }t        t        j                  |            dx}x}}y)u   gh_runner stub: check-runs에 gemini-review-gate 없음 → check_present=False.
    git_exit_128=True 시나리오: preflight에서 exit 128 발생으로 gate의 내부 원인 포함.
    sha_for_gate_testr   <   )max_wait_secondsr   Frj   rl   rm   z"Expected check_present=False, got rp   rq   Ncheck_missingr   inz%(py1)s in %(py4)srv   r&   )>=)z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} >= %(py7)slen)py0py2ro   py7zassert %(py9)spy9c                *    d| v rdddddidgiS diS )	NrT   rJ   gemini-review-gaterU   rV   z"fatal: exit 128 unable to read refrW   rI   r8   )rK   argsrZ   s     r   gh_with_gate_exit128zVtest_gemini_review_gate_check_missing_60s_internal_cause.<locals>.gh_with_gate_exit128   s>    8# 4&/#)+O"P  h''r   Tr   rK   rO   r   r5   r6   r7   )r[   r"   'verify_gemini_review_gate_check_presentrz   r{   r|   r}   r~   r   r   r   r   r   )r   r   gate_resultr   r   r   r   r   @py_assert1@py_assert6@py_assert5@py_format8@py_format10r   mod2gate_result2rZ   s                   @r   8test_gemini_review_gate_check_missing_60s_internal_causer      s    #H28<I

+C==hY[=\K'd5d'50ddd'5ddd'ddd5ddd4VWbVc2ddddddddEk*DEE?EEEEE?EEEE?EEEEEEEEEEE
 56<367<1<71<<<<71<<<<<<3<<<3<<<6<<<7<<<1<<<<<<<( "67D??[]?^L(0D0(D0000(D000(000D0000000'/4/'4////'4///'///4///////E\*DEE>EEEEE>EEEE>EEEEEEEEEEEr   z3anu_v2.pr_open_gemini_trigger_prevention.time.sleepc                   d}d}t        j                  ||z        }ddid&fd}t        ||      }t        ||      }|j	                  dd|	      }|d
   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	d   }||k  }
|
st        j                  d|
fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  dd    d| d      dz   d|iz  }t        t        j                  |            dx}}
ddddddd}dddd}dddg d}|j                  ||||      }|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$   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	|d%   }d}	||	u }
|
slt        j                  d|
fd||	f      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            dx}x}
}	y)'u&  evidence_poller stub: 항상 [] (gemini 응답 0). clock stub으로 grace_seconds=180 만료.
    poller call_count <= ceil(180/30) = 6 (25분 polling/self-register 절대 부재 검증).
    classify → EXTERNAL_TRIGGER_REQUIRED (head_match=True, fetchable=True, evidence_arrived=False).
          r$   r   c                "    dxx   dz  cc<   g S )Nr$   r&   r8   )rM   rZ   r3   s     r   poller_stubzEtest_first_evidence_grace_window_expiry_classify.<locals>.poller_stub  s    31	r   )r   r   *   sha_grace_test)rM   rZ   grace_secondsr   Frj   rl   rm   rv   rq   N)<=)z%(py1)s <= %(py3)s	MAX_CALLSr   u   poller 호출 횟수 u   가 hard limit u6   를 초과. 25분 polling 재발 패턴 금지 위반.
>assert %(py5)sr   Tr   )r   r   r   r   r   rs   r   r	   r   r   r   )rM   intrZ   rO   r6   
list[dict])mathceilrf   r"   poll_first_gemini_evidencerz   r{   r|   r~   r   r   r   r   r}   r   r	   )_mock_sleepGRACEINTERVALr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r3   s                      @r   0test_first_evidence_grace_window_expiry_classifyr     s    EH		%(*+IqJ
  x0E
{%
@C--EU=B . DH &'050'50000'5000'00050000000c? ?i'  ?i          (    (     
30	{ K5 	5     E"TTXZI4DTdeJ%)5PT024L ..y*lT\]N*+W+/WWWWW+/WWWW+WWWWWW/WWWW/WWWWWWWW./858/58888/5888/88858888888,'/4/'4////'4///'///4///////r   c                   t        d      }|d   |d   |d   }|d   |d   d2fd}d3fd}d4d	}d
dddd5fd}g d6fd}t        |||||      }|j                  |      }	|	d   }
|d   }|
|k(  }|st        j                  d|fd|
|f      t        j
                  |
      t        j
                  |      dz  }t        j                  d|d    d|	d          dz   d|iz  }t        t        j                  |            dx}
x}}|	d   }
|d   }|
|k(  }|st        j                  d|fd|
|f      t        j
                  |
      t        j
                  |      dz  }t        j                  d|d    d|	d          dz   d|iz  }t        t        j                  |            dx}
x}}|	d   }
|d    }|
|k(  }|st        j                  d|fd|
|f      t        j
                  |
      t        j
                  |      dz  }t        j                  d!|d     d|	d          dz   d|iz  }t        t        j                  |            dx}
x}}|d"   j                  d#      d   }
|	d$   }|
|v }|st        j                  d%|fd&|
|f      t        j
                  |
      t        j
                  |      dz  }t        j                  d'|d"   j                  d#      d    d(|	d$    d)      dz   d|iz  }t        t        j                  |            dx}
x}}t              }d*}||k(  }|st        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  }t        j                  d/      d0z   d1|iz  }t        t        j                  |            dx}x}}y)7un   _load_fixture("pr_open_gemini_miss_pr86") 기반 모든 stub 셋업 → run() 결과 fixture 기대값 일치.pr_open_gemini_miss_pr86rM   base_branchhead_branchhead_sha_at_openr   c                    dj                  |       }d|v rd|v r	|vrdddS d|v r	|v rdddS d|v rd |v rdd	dS d|v rdd
dS d|v rdddS dd
dS )Nr'   r(   originr   r)   r*   r-   zorigin/1r.   r<   abcr/   )r   r2   r   rZ   s     r   git_runner_pr86zDtest_pr86_fixture_external_trigger_required.<locals>.git_runner_pr86K  s    hhtnc>h#o(#2E!"b11c>h#o
 "#b117;-!8C!?!"c22!"c223!"e44#..r   c                <    d | v rdiS d| v rdddddid	giS i S )
NrH   rI   rT   rJ   r   rU   rV   r)   rW   r8   )rK   r   rM   r   s     r   gh_runner_pr86zCtest_pr86_fixture_external_trigger_required.<locals>.gh_runner_pr86`  sR    I;8+ *--8#  4&/#)2,  	r   c                    g S Nr8   
pr_number_	head_sha_s     r   poller_pr86z@test_pr86_fixture_external_trigger_required.<locals>.poller_pr86q  s    	r   r   r^   r   )r]   callsc                 |    d   } dxx   dz  cc<   d   dk\  rt        dz         d<   | S dxx   dz  cc<   | S )Nr]   r   r&      r   )rd   )vclock_stategraces    r   
clock_pr86z?test_pr86_fixture_external_trigger_required.<locals>.clock_pr86w  s[    G!w1$$UQY/K  "r   c                :    j                  t        |              y r   )appendr7   )recaudit_recordss    r   r   zAtest_pr86_fixture_external_trigger_required.<locals>.audit_writer  s    T#Y'r   )r   r   r   r   r   rM   r   r   rZ   r   r   expected_classificationrs   ru   rm   	Expected , got rp   rq   Nr   expected_auto_retry_allowedzExpected auto_retry_allowed=r   expected_human_onlyzExpected human_only=expected_next_action_r   r   r   z!Expected next_action to contain 'z', got ''r&   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   r
  )r   rn   r   rq   u2   audit_writer가 정확히 1회 호출되어야 함z
>assert %(py8)spy8r   r5   r6   r7   r   r   r   r   rO   r6   r   rc   )r	  r7   r6   None)r   r"   runrz   r{   r|   r}   r~   r   splitr   r   r   r   )r   fxr   r   r   r   r  r   r   r   r   r   r   r   r   r   @py_assert4@py_format9r
  r   r  r  rZ   rM   r   s                     @@@@@@@r   +test_pr86_fixture_external_trigger_requiredr  ?  s&    
1	2B_I-(K-(K)*H&J/*" Ea(K !#M(  "#!C WW  F "# r*C'D #'DD  #'D    $    (E    B012&@P9Q8RS     &' 2.K+L '+LL  '+L    (    ,M    'r*G'H&IPVWkPlOmn     , 2&;#< #<<  #<         $=    r"789|@T?UV     $%++C03 vm7L 37LL  37L    4    8M    ,B/E,F,L,LS,QRS,T+U V}%&a	)    
 }XX"XXXXXXXXX3XXX3XXXXXX}XXX}XXXXXXXXX$XXXXXXXXr   c                B   ddd#d}d$fd}d%d}ddid&fd}t        ||||	      }|j                  d
dd      }|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  }	t        j                  dt         d|d          dz   d|	iz  }
t        t        j                  |
            dx}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|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}}y)'u   모든 stub 정상: head_match=True, fetchable=True, behind=0, evidence_arrived=True
    → classification == PR_OPEN_GEMINI_TRIGGER_OK, auto_retry_allowed=False, human_only=False.
    normalshaok1234d   c                d    dj                  |       }d|v rdddS d|v rdddS d|v rdd	dS dddS )
Nr'   r(   r   r)   r*   r-   r.   r<   base123r/   )r   r2   s     r   git_runner_okz<test_normal_pr_open_classification_ok.<locals>.git_runner_ok  sR    hhtnc>!"b11!"c223!"i88"--r   c                <    d | v rdiS d| v rdddddid	giS i S )
NrH   rI   rT   rJ   r   r   rV   r)   rW   r8   )rK   r   rZ   rM   s     r   gh_runner_okz;test_normal_pr_open_classification_ok.<locals>.gh_runner_ok  sP    I;8+ (++8# 4&/#)2,  	r   c                    dddi|dgS )Ni'  loginzgemini-code-assist[bot])iduser	commit_idr8   r   s     r   	poller_okz8test_normal_pr_open_classification_ok.<locals>.poller_ok  s"      ";<&
 	
r   r]   r^   c                 ,    d   } dxx   dz  cc<   | S )Nr]   r   r8   )r  r  s    r   clock_okz7test_normal_pr_open_classification_ok.<locals>.clock_ok  s"    CBr   )r   r   r   r   rh   z
feature/okr   r  r   rs   r   r   r   r  r  r   r   Nr   Frj   rl   rm   rv   rq   r   r   proceed_to_merge_gatesru   r  r   r  rc   )r"   r  r   rz   r{   r|   r   r   r   r}   r~   r   )r   r#  r%  r+  r-  r   r   r   r   r   r   r   r   r   r  rZ   rM   s                 @@@r   %test_normal_pr_open_classification_okr/    s!   
 !HI. 
 *K
  !	C WW   F "# #'88  #'8    $      (9    (9    %&fV4D-E,FG     &'050'50000'5000'00050000000,(5(5((((5((((((5(((((((- <$<< $<<<<< $<<<< <<<$<<<<<<<<r   c                 .	   t        d      } | d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d| d          dz   d	|iz  }t        t        j                  |            d
x}x}}| d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d| d          dz   d	|iz  }t        t        j                  |            d
x}x}}| j                  dd      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|      dz   d|iz  }t        t        j                  |            d
x}}t        j                  t              }	d}||	v}|st        j                  d|fd||	f      t        j                  |      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }t        j                  d      dz   d|iz  }t        t        j                  |            d
x}}d}||	v}|st        j                  d|fd||	f      t        j                  |      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }t        j                  d      dz   d|iz  }t        t        j                  |            d
x}}g d}
|
D ]  }||	v}|st        j                  d|fd ||	f      d!t        j                         v st        j                  |      rt        j                  |      nd!dt        j                         v st        j                  |	      rt        j                  |	      ndd"z  }t        j                  d#| d$      d%z   d&|iz  }t        t        j                  |            d
} y
)'uM  _load_fixture("post_merge_audit_warn_to_pass_pr86") + 본 모듈 외부 인터페이스 spec(§4) 위반 0건 확인.

    검증 항목:
    a. fixture["initial_audit_verdict"] == "POST_MERGE_AUDIT_WARN"
    b. fixture["final_audit_verdict_after_backfill"] == "POST_MERGE_AUDIT_PASS"
    c. fixture["lesson_pinned"]에 "marker 부재" 포함
    d. 본 모듈 source에 ".smoke-evidence" 키워드 부재 (smoke 검사 직접 수행 안 함)
    e. 본 모듈 source에 ".reconcile-evidence" 키워드 부재
    f. isolation 검증: utils/dispatch/scripts/dashboard 키워드 부재
    "post_merge_audit_warn_to_pass_pr86initial_audit_verdictPOST_MERGE_AUDIT_WARNrs   ru   rm   z$Expected POST_MERGE_AUDIT_WARN, got rp   rq   N"final_audit_verdict_after_backfillPOST_MERGE_AUDIT_PASSz$Expected POST_MERGE_AUDIT_PASS, got lesson_pinnedr)   u   marker 부재r   )z%(py1)s in %(py3)slessonr   u,   lesson_pinned에 'marker 부재' 미포함: r   r   z.smoke-evidence)not in)z%(py1)s not in %(py3)ssrcu   PROpenGeminiTriggerPrevention 소스에 '.smoke-evidence' 키워드가 있어서는 안 됨. smoke 검사는 후속 task 책임.z.reconcile-evidenceu   PROpenGeminiTriggerPrevention 소스에 '.reconcile-evidence' 키워드가 있어서는 안 됨. reconcile 검사는 후속 task 책임.)z
from utilszimport utilszfrom dispatchzimport dispatchzfrom scriptszimport scriptszfrom dashboardzimport dashboard)z%(py0)s not in %(py2)skw)r   r   u:   PROpenGeminiTriggerPrevention 소스에 금지된 import 'u)   '가 발견됨. one-way isolation 위반.z
>assert %(py4)sro   )r   rz   r{   r|   r}   r~   r   getr   r   r   inspect	getsourcer   )r  r   r   r   r   r   r7  r   r   r9  forbidden_keywordsr:  r   @py_format3s                 r   2test_post_merge_audit_warn_to_pass_spec_compliancer@    s$    
;	<B %& *A &*AA  &*A    '    +B    /r2I/J.KL    
 23 7N 37NN  37N    4    8O    /r2V/W.XY    
 VVOR(F ?f$  ?f          %    %    7vjA    
 

9
:C  C'  C          %(    %(   	.     !  +       !      ),    ),   	2    b  
} 	
 	
r 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
6	
 	
   	
 	
 
	  	
 	
  I M( (	
 	
 	
 	
 	

r   )r   rO   r6   r7   )r    rO   r6   r   )r6   r   )rM   r   rP   rO   rL   rO   r6   r   )rZ   rO   r6   r   )r   )re   r   ra   r   r6   r   )r6   r  )4__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriterz   r<  r   r   syspathlibr   typingr   unittest.mockr   r   __file__resolveparentsWORKSPACE_ROOTrO   pathinsert(anu_v2.pr_open_gemini_trigger_preventionr	   r
   r   r   r   parentr   r   r"   r9   r@   rE   rQ   r[   rf   r   r   r   r   r   r   r  r/  r@  r8   r   r   <module>rS     s^    #      
   % h'')11!4~chh&HHOOAs>*+  H~$$++j8I 
"  #( 2	#+4D<$FR <='0 >'0X <=cY >cYP <=E= >E=T3
r   