
    jT                       U d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m
Z
 ddlmZ ddlmZmZmZ ddlmZ d	Zd
ed<   dZd
ed<    G d de      Z G d de
      Z eh d      Zded<    eh d      Zded<    eh d      Zded<    eh d      Zded<   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dPdZdQdZ	 	 	 	 	 	 	 	 	 	 	 	 dRdZ dQdZ! ed        G d! d"             Z"dSd#Z#dSd$Z$dTd%Z%dSd&Z&dd'	 	 	 	 	 dUd(Z'ejP                  jR                  d)ejT                  jR                  d*ejV                  jR                  d+ejX                  jR                  d,ejZ                  jR                  d-ej\                  jR                  d.iZ/d/ed0<   d1Z0d
ed2<   	 dd3	 	 	 	 	 	 	 	 	 dVd4Z1d5Z2d
ed6<   	 dd7	 	 	 	 	 dWd8Z3 ed9      Z4d:ed;<   	 	 dXdd3	 	 	 	 	 	 	 dYd<Z5d=Z6d>ed?<   	  ed        G d@ dA             Z7dZdBZ8	 	 	 	 	 	 	 	 	 	 	 	 	 	 d[dCZ9ddD	 	 	 	 	 d\dEZ: ed        G dF dG             Z;d]dHZ< ed        G dI dJ             Z=d^dKZ>d_dLZ?d`dMZ@dadNZAg dOZBy)bu@  anu_v2.second_review_recovery — GEMINI_SECOND_REVIEW_BOTTLENECK 자동 복구 모듈 (task-2565).

follow-up commit 이후 Gemini evidence 가 old head 에만 존재하고 current head 에는 없어
CI gate 가 SHA mismatch 로 실패하는 병목을 코드/파일 자동화로 고정한다.

9 상태 enum (SecondReviewState):
  FOLLOW_UP_COMMIT_CREATED → GEMINI_EVIDENCE_STALE_ON_HEAD → SECOND_REVIEW_PENDING
  → SECOND_REVIEW_TRIGGER_REQUIRED → SECOND_REVIEW_OWNER_TRIGGER_POSTED
  → SECOND_REVIEW_FRESH_DETECTED → CI_RERUN_REQUIRED_AFTER_FRESH
  → MERGE_READY_AFTER_SECOND_REVIEW
  (timeout 분기: SECOND_REVIEW_TIMEOUT)

schema 버전:
  second_review_decision.v1  (상수: SECOND_REVIEW_DECISION_SCHEMA)
  pre_merge_commit_decision.v1 (상수: PRE_MERGE_COMMIT_DECISION_SCHEMA)

불변 원칙 (task-2565 §8):
  - manual_owner_input_requested 항상 False — 회장 수동 요청 금지 doctrine
  - long_polling_used 항상 False — long polling 금지 (task-2556 §9)

one-way isolation: anu_v2/ 외부 import 금지. stdlib + enum + json + pathlib 만 사용.
    )annotationsN)	dataclassfield)Enum)Path)AnyCallableFinal)SECOND_REVIEW_GRACE_SECONDSz anu_v2.second_review_decision.v1z
Final[str]SECOND_REVIEW_DECISION_SCHEMAz#anu_v2.pre_merge_commit_decision.v1 PRE_MERGE_COMMIT_DECISION_SCHEMAc                      e Zd ZdZy)SchemaViolationu_   schema 검증 실패 — 필수 필드 누락, 잘못된 enum 값, 또는 불변 원칙 위반.N)__name__
__module____qualname____doc__     ?/home/jay/workspace/scripts/../anu_v2/second_review_recovery.pyr   r   0   s    ir   r   c                  4    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
Zy)SecondReviewStateu  GEMINI_SECOND_REVIEW_BOTTLENECK 자동화 상태 머신 9 상태 (task-2565 §2).

    전이 순서:
      FOLLOW_UP_COMMIT_CREATED
      → GEMINI_EVIDENCE_STALE_ON_HEAD
      → SECOND_REVIEW_PENDING
      → SECOND_REVIEW_TRIGGER_REQUIRED
      → SECOND_REVIEW_OWNER_TRIGGER_POSTED
      → SECOND_REVIEW_FRESH_DETECTED
      → CI_RERUN_REQUIRED_AFTER_FRESH
      → MERGE_READY_AFTER_SECOND_REVIEW
      (timeout 분기: → SECOND_REVIEW_TIMEOUT)
    FOLLOW_UP_COMMIT_CREATEDGEMINI_EVIDENCE_STALE_ON_HEADSECOND_REVIEW_PENDINGSECOND_REVIEW_TRIGGER_REQUIRED"SECOND_REVIEW_OWNER_TRIGGER_POSTEDSECOND_REVIEW_FRESH_DETECTEDSECOND_REVIEW_TIMEOUTCI_RERUN_REQUIRED_AFTER_FRESHMERGE_READY_AFTER_SECOND_REVIEWN)r   r   r   r   r   r   r   r   r   r   r   r    r!   r   r   r   r   r   6   s?      :$C!3%E")M&#A 3$C!&G#r   r   >   NONEOUTDATED_THREAD_ON_OLD_HEAD#GEMINI_EVIDENCE_NOT_ON_CURRENT_HEAD#SHA_MISMATCH_AFTER_FOLLOW_UP_COMMITzFinal[frozenset[str]]_VALID_STALE_REASON>   r"   SHA_MISMATCHGEMINI_EVIDENCE_STALE_VALID_CI_GATE_REASON>   TIMEOUTWAITING	ESCALATEDMERGE_READYFRESH_DETECTED_VALID_FINAL_DECISION>   code_fixtest_fixreport_onlyevidence_required_VALID_CHANGE_TYPEc                n    i dt         d| d|d|d|d|d|d|d	|d
|d|	ddddd|
d|d|S )u  ``second_review_decision.v1`` schema dict 를 생성한다 (task-2565 §4.1).

    박제 필드 (절대 변경 금지):
      - ``manual_owner_input_requested``: 항상 False (회장 수동 요청 금지 doctrine)
      - ``long_polling_used``: 항상 False (long polling 금지)
      - ``schema``: SECOND_REVIEW_DECISION_SCHEMA

    Args:
      task_id: 관련 task ID (예: "task-2565").
      pr_number: PR 번호.
      old_head_sha: follow-up commit 이전 head SHA.
      current_head_sha: 현재 head SHA.
      latest_gemini_commit_id: 최신 Gemini review commit_id.
      gemini_fresh_on_current_head: current head 기준 fresh evidence 존재 여부.
      stale_reason: stale 판정 이유 (허용 값: _VALID_STALE_REASON).
      ci_gate_reason: CI gate 실패 이유 (허용 값: _VALID_CI_GATE_REASON).
      owner_trigger_required: owner_trigger_only 자동 호출 필요 여부.
      owner_trigger_result: "POSTED" | "DEDUPED" | "FAILED" | "NONE".
      fresh_detected_at: fresh evidence 감지 ISO timestamp 또는 None.
      ci_rerun_required: CI rerun 필요 여부.
      final_decision: 최종 결정 (허용 값: _VALID_FINAL_DECISION).

    Returns:
      검증되지 않은 decision dict (validate_second_review_decision 으로 후속 검증 권장).
    schematask_id	pr_numberold_head_shacurrent_head_shalatest_gemini_commit_idgemini_fresh_on_current_headstale_reasonci_gate_reasonowner_trigger_requiredowner_trigger_resultmanual_owner_input_requestedFlong_polling_usedfresh_detected_atci_rerun_requiredfinal_decision)r   )r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rC   rD   rE   s                r   build_second_review_decisionrF   q   s    R/7 	Y 		
 	, 	"#: 	'(D 	 	. 	!"8 	 4 	' 	U 	. 	.  	.! r   c                   h d}|t        | j                               z
  }|rt        dt        |             | d   t        k7  rt        dt        d| d         | d   durt        d      | d	   durt        d
      | d   t
        vr!t        d| d   dt        t
                     | d   t        vr!t        d| d   dt        t                     | d   t        vr!t        d| d   dt        t                     y)u  ``second_review_decision.v1`` schema 검증 (task-2565 §4.1).

    검증 항목:
      1. schema 일치
      2. manual_owner_input_requested 항상 False
      3. long_polling_used 항상 False
      4. 필수 필드 전수 존재
      5. stale_reason, ci_gate_reason, final_decision enum 허용 값

    Args:
      decision: build_second_review_decision 으로 생성된 dict.

    Raises:
      SchemaViolation: 위반 항목 발견 시.
    >   r6   r7   r8   r9   r=   r>   rE   r:   rD   rC   rB   r@   r?   r;   r<   rA   u&   second_review_decision 누락 필드: r6      schema 불일치: expected=, got=rA   Fum   manual_owner_input_requested must always be False — 회장 수동 요청 금지 doctrine (task-2565 §8 #1)rB   u`   long_polling_used must always be False — long polling 금지 (task-2556 §9, task-2565 §8 #4)r=   u    stale_reason 허용 값 위반:  not in r>   u"   ci_gate_reason 허용 값 위반: rE   u"   final_decision 허용 값 위반: N)setkeysr   sortedr   r&   r)   r/   decision_required_fieldsmissings      r   validate_second_review_decisionrR      s     X]]_!55G4VG_4EF
 	

 ::)*G)J KH%(*
 	
 ./u<J
 	
 #$E1H
 	
 '::.x/G.J K0124
 	
  !)>>0:J1K0N O2346
 	
  !)>>0:J1K0N O2346
 	
 ?r   c                    t         | ||||dS )u6  ``pre_merge_commit_decision.v1`` schema dict 를 생성한다 (task-2565 §4.2).

    Args:
      merge_ready: MERGE_READY 5+1 조건 충족 여부.
      changed_files: 변경 파일 목록.
      change_type: 변경 유형 (허용: "report_only","code_fix","test_fix","evidence_required").
      pre_merge_commit_allowed: merge 전 commit 허용 여부.
      redirect_to_post_merge_evidence: post-merge evidence redirect 활성화 여부.

    Returns:
      검증되지 않은 decision dict (validate_pre_merge_commit_decision 으로 후속 검증 권장).
    r6   merge_readychanged_fileschange_typepre_merge_commit_allowedredirect_to_post_merge_evidence)r   )rU   rV   rW   rX   rY   s        r   build_pre_merge_commit_decisionrZ      s     * 3"&"$<+J r   c                   h d}|t        | j                               z
  }|rt        dt        |             | d   t        k7  rt        dt        d| d         | d   t
        vr!t        d| d   dt        t
                     y	)
uF  ``pre_merge_commit_decision.v1`` schema 검증 (task-2565 §4.2).

    검증 항목:
      1. schema 일치
      2. 필수 필드 전수 존재
      3. change_type 허용 값

    Args:
      decision: build_pre_merge_commit_decision 으로 생성된 dict.

    Raises:
      SchemaViolation: 위반 항목 발견 시.
    >   r6   rW   rU   rV   rX   rY   u)   pre_merge_commit_decision 누락 필드: r6   rH   rI   rW   u   change_type 허용 값 위반: rJ   N)rK   rL   r   rM   r   r4   rN   s      r   "validate_pre_merge_commit_decisionr\     s     X]]_!55G7w7HI
 	

 ==)*J)M NH%(*
 	
 &88-h}.E-H I/013
 	
 9r   T)frozenc                  z    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   ded<   ded<   ded<   dZded<   y)SecondReviewInputu  second-review 판정 입력 (caller가 채워서 주입).

    Attributes:
      task_id: 관련 task ID (예: "task-2565").
      pr_number: PR 번호.
      old_head_sha: follow-up commit 이전 head SHA.
      current_head_sha: 현재 head SHA.
      latest_gemini_commit_id: 최신 Gemini review commit_id (없으면 None).
      ci_gate_failure_reason: CI gate 실패 이유 — "GEMINI_EVIDENCE_STALE" | "SHA_MISMATCH" | None.
      unresolved_thread_outdated: unresolved thread가 old head 기준 OUTDATED인지.
      follow_up_commit_detected: follow-up commit 감지 여부.
      elapsed_since_follow_up_seconds: follow-up 발생 후 경과 (caller 계산).
      owner_trigger_audit_entries: 기존 audit entries (dedupe 검사용).
    strr7   intr8   r9   r:   
str | Noner;   ci_gate_failure_reasonboolunresolved_thread_outdatedfollow_up_commit_detectedelapsed_since_follow_up_secondsr   ztuple[dict, ...]owner_trigger_audit_entriesN)r   r   r   r   __annotations__rh   r   r   r   r_   r_   F  sI     LN''&& $$##%((46!16r   r_   c                   | j                   xr | j                  | j                  k7  }t        | j                        xr | j                  | j                  k7  }| j
                  dv }| j                  }t        |xr
 |xr |xr |      S )u  4조건 모두 충족 시 True — Gemini evidence가 follow-up commit 이후 stale 상태임을 판정.

    4조건:
      1. follow-up commit 감지 + head SHA 변경
      2. latest_gemini_commit_id 존재 + current head에 없음
      3. CI gate 실패 이유가 GEMINI_EVIDENCE_STALE 또는 SHA_MISMATCH
      4. unresolved thread가 old head 기준 OUTDATED

    Args:
      inp: SecondReviewInput 판정 입력.

    Returns:
      4조건 모두 True면 True, 하나라도 False면 False.
    )r(   r'   )rf   r:   r9   rd   r;   rc   re   inpcond1cond2cond3cond4s        r   &is_gemini_stale_on_head_after_followuprq   c  s     ))Vc.B.BcFVFV.VE,,-e#2M2MQTQeQe2eE&&*SSE**E3%3E3e44r   c                (    | j                   t        k\  S )u   follow-up commit 이후 grace period가 경과했는지 판정.

    Args:
      inp: SecondReviewInput 판정 입력.

    Returns:
      elapsed_since_follow_up_seconds >= SECOND_REVIEW_GRACE_SECONDS 이면 True.
    )rg   r   rl   s    r   grace_period_elapsedrt   y  s     ..2MMMr   c                    | j                   rt        |       st        j                  S t        |       r+t	        |       rt        j
                  S t        j                  S t        j                  S )u   상태 전이 결정 함수 — SecondReviewInput을 받아 현재 상태를 결정.

    전이 우선순위:
      1. follow_up_commit만 감지 → FOLLOW_UP_COMMIT_CREATED
      2. 4조건 True + grace 경과 → SECOND_REVIEW_TRIGGER_REQUIRED
      3. 4조건 True + grace 미경과 → SECOND_REVIEW_PENDING
      4. 4조건 True → GEMINI_EVIDENCE_STALE_ON_HEAD (grace 로직은 2/3에서 처리)

    Args:
      inp: SecondReviewInput 판정 입력.

    Returns:
      SecondReviewState enum 값.
    )rf   rq   r   r   rt   r   r   r   rs   s    r   determine_staterv     sZ      $$-STW-X 999 .c2$$CCC$::: :::r   c                :   | j                    d| j                   }| j                  D ]q  }|j                  d      xs |j                  d      xs d d|j                  d      xs |j                  d      xs d }||k(  s\|j                  d      dk(  sq y	 y
)u=  same-head dedupe 판정 — 동일 (pr_number, current_head_sha) POSTED 이미 존재하는지 확인.

    dedupe_key 포맷: "{pr_number}+{current_head_sha}"

    Args:
      inp: SecondReviewInput 판정 입력.

    Returns:
      동일 dedupe key로 result=POSTED 항목이 audit_entries에 있으면 True.
    +r8   pr head_shaheadresultPOSTEDTF)r8   r:   rh   get)rl   
dedupe_keyentry	entry_keys       r   is_owner_trigger_dedupedr     s     MM?!C$8$8#9:J00 yy-F4FBGqS]I^IybgbkbklrbsIywyHz{	
"uyy':h'F r   )trigger_callablec                  | j                    d| j                   }t        |       sdd|ddS t        |       sdd|d| j                   dt
         d	dS t        |       r"dd
|d| j                    d| j                   ddS |dd|ddS 	  || j                   | j                         dd|d| j                    d| j                   ddS # t        $ r*}dd|dt        |      j                   d| dcY d}~S d}~ww xY w)u	  owner trigger 자동 호출 진입점 (4조건 + grace + dedupe 확인 후 호출).

    흐름:
      1. 4조건 미충족 → CONDITIONS_NOT_MET
      2. grace 미경과 → GRACE_PENDING
      3. dedupe True → DEDUPED
      4. trigger_callable None → NOT_TRIGGERED (테스트용)
      5. trigger_callable 호출 → POSTED (성공) / FAILED (예외)

    Args:
      inp: SecondReviewInput 판정 입력.
      trigger_callable: (pr_number, head_sha) → dict 형태의 호출 가능 객체.
        None이면 NOT_TRIGGERED 반환 (테스트/dry-run용).

    Returns:
      dict with keys:
        - triggered: bool
        - result: "POSTED"|"DEDUPED"|"FAILED"|"NOT_TRIGGERED"|"GRACE_PENDING"|"CONDITIONS_NOT_MET"
        - dedupe_key: str
        - reason: str
    rx   FCONDITIONS_NOT_METu)   4조건 미충족 — stale 상태 아님)	triggeredr}   r   reasonGRACE_PENDINGu   grace period 미경과 — zs < sDEDUPEDu   동일 (pr=z, head=u   ) POSTED 이미 존재NNOT_TRIGGEREDu(   trigger_callable=None — dry-run 모드Tr~   u    owner trigger 호출 성공 (pr=)FAILEDu   trigger_callable 예외: : )
r8   r:   rq   rt   rg   r   r   	Exceptiontyper   )rl   r   r   excs       r   auto_trigger_owner_reviewr     s_   4 MM?!C$8$8#9:J 2#6*$A	
 	
  $%$-667t<W;XXY[
 	
  $$#CMM?'#:N:N9OOef	
 	
 %$@	
 	

(<(<=$8wsOcOcNddef	
 	
  
$1$s)2D2D1ERuM	
 	

s   >C 	C>C93C>9C>z-task-2565.gemini-stale-on-head-after-followupz/task-2565.second-review-owner-trigger-requestedz,task-2565.second-review-owner-trigger-postedz&task-2565.second-review-fresh-detectedztask-2565.ci-rerun-after-freshz)task-2565.merge-ready-after-second-reviewzFinal[dict[str, str]]_STATE_MARKER_MAPz%task-2565.second-review-decision.jsonSECOND_REVIEW_DECISION_JSON
marker_dirc               v   g }t        h d      }|t        |j                               z
  }|rt        dt	        |       d      t
        j                  |j                        }|rt        ||      }|j                  |       | |j                  d|}	t        t        |	|      }|j                  |       |S )u  상태별 marker 파일 생성 + second-review-decision.json 갱신.

    상태 → marker 파일 매핑:
      GEMINI_EVIDENCE_STALE_ON_HEAD → task-2565.gemini-stale-on-head-after-followup
      SECOND_REVIEW_TRIGGER_REQUIRED → task-2565.second-review-owner-trigger-requested
      SECOND_REVIEW_OWNER_TRIGGER_POSTED → task-2565.second-review-owner-trigger-posted
      SECOND_REVIEW_FRESH_DETECTED → task-2565.second-review-fresh-detected
      CI_RERUN_REQUIRED_AFTER_FRESH → task-2565.ci-rerun-after-fresh
      MERGE_READY_AFTER_SECOND_REVIEW → task-2565.merge-ready-after-second-review

    또한 항상 task-2565.second-review-decision.json 갱신.

    Args:
      task_id: 관련 task ID.
      state: 현재 SecondReviewState.
      decision: auto_trigger_owner_review 반환 dict 또는 임의 decision dict.
      marker_dir: marker 저장 디렉토리. None이면 MARKER_DIR 사용.

    Returns:
      생성/갱신된 marker 경로 list.
    >   r6   r8   r9   r=   r>   rE   r:   rD   rC   rB   r@   r?   r;   r<   rA   u,   emit_phase2_markers: decision 누락 필드 uR    — build_second_review_decision()의 완전한 결과를 전달해야 합니다.r   )r7   state)payloadr   )	frozensetrK   rL   
ValueErrorrM   r   r   valuewrite_markerappendr   )
r7   r   rO   r   created_SCHEMA_V1_REQUIREDmissing_fieldsmarker_namepdecision_payloads
             r   emit_phase2_markersr   !  s    8 G
 +4 5 + )3x}}+??N:6.;Q:R S\ \
 	
 $''4K<q ( (
 	0:JWabANN1Nr   z&memory/audit/owner_trigger_audit.jsonl_DEFAULT_AUDIT_REL_PATH)
audit_pathc                  	 ddl }ddl}||nt	        t
              j                         }|j                  j                  dd       t        |dd      5 }|j                  |j                         |j                         	 |j                  t        j                  | d	d
      dz          |j!                           |j"                  |j                                |j                  |j                         |j$                         	 ddd       |j                         S # t        $ r}t        d      |d}~ww xY w# |j                  |j                         |j$                         w xY w# 1 sw Y   |j                         S xY w)u  owner_trigger audit JSONL에 한 줄 atomic append.

    기본 경로: memory/audit/owner_trigger_audit.jsonl
    entry는 dict (timestamp, pr_number, head_sha, result, source 등 포함 권장).

    Args:
      entry: audit 기록 dict. timestamp/pr_number/head_sha/result/source 포함 권장.
      audit_path: JSONL 파일 경로. None이면 기본 경로 사용.

    Returns:
      audit 파일 절대 경로.

    Raises:
      OSError: 디렉토리 생성 또는 파일 기록 실패 시.
    r   Nzappend_owner_trigger_audit requires POSIX fcntl (Linux only). ANU CI is Linux self-hosted; non-Linux environments not supported.Tparentsexist_okautf-8)encodingF)ensure_ascii	sort_keys
)fcntlImportErrorNotImplementedErrorosr   r   resolveparentmkdiropenflockfilenoLOCK_EXwritejsondumpsflushfsyncLOCK_UN)r   r   r   er   targetfhs          r   append_owner_trigger_auditr   i  s2   . &2j=T8U^^`F
MMt4	fcG	, 4BIIK/	4HHTZZETJTQRHHJBHHRYY[!KK		U]]34 >>'  !Q
 	" KK		U]]34 >>s<   D +E+AD<*E+	D9(D44D9<,E((E++Fzmemory/eventszFinal[Path]
MARKER_DIRc                  ||nt         j                         }|j                  dd       || z  }| j                  d      r||ni }t	        j
                  |dd      }t        j                  d|d	|  d	d
dd      }t        |j                        }	 |5  |j                  |       |j                          t        j                  |j                                ddd       t        j                  ||       |j                         S |j%                  d       |j                         S # 1 sw Y   QxY w# t         $ r |j#                  d        w xY w)u  marker 파일을 생성하고 절대 경로를 반환한다.

    Args:
      marker_name: marker 파일 이름.
        ``.json`` 으로 끝나면 payload 를 JSON 으로 기록.
        그 외이면 빈 파일 touch (payload 무시).
      payload: JSON marker 일 때 기록할 dict. None 이면 {} 로 기록.
      marker_dir: marker 저장 디렉토리. None 이면 ``MARKER_DIR`` 사용.

    Returns:
      생성된 marker 파일의 절대 경로.

    Raises:
      OSError: 디렉토리 생성 또는 파일 기록 실패 시.
    NTr   z.jsonF   )r   indentw.z.tmpr   )dirprefixsuffixdeleter   )
missing_ok)r   )r   r   r   endswithr   r   tempfileNamedTemporaryFiler   namer   r   r   r   r   replacer   unlinktouch)	r   r   r   
target_dirmarker_pathdata
serializedtftmp_paths	            r   r   r     sD   * !+ 6*JOOQJTD1{*KG$!-w2ZZ5C
(({m1%
 =	 &$
%& JJx-    	4(  & &
  	OOtO,	s%   D8 AD,D8 ,D51D8 8E   z
Final[int]MAX_CI_RERUN_PER_HEADc                  j    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   dZded<   dZded<   y)CIRerunInputu  CI rerun 자동 판정 입력 (caller가 채워서 주입).

    Attributes:
      pr_number: PR 번호.
      current_head_sha: 현재 head SHA.
      gemini_fresh_on_current_head: 최신 review commit_id == current head 여부.
      ci_gate_failed_with_stale_reason: failure_reason이 EVIDENCE_STALE 또는 SHA_MISMATCH인지.
      current_head_unchanged_since_failure: CI 실패 이후 head SHA 변경 없음 여부.
      unresolved_thread_count: 미해결 thread 수 (0 = 허용, >0 = triage 별도 판정).
      failed_job_identifiers: 실패한 CI job 식별자 목록.
      previous_rerun_attempts: 같은 (head_sha, job_id) 누적 rerun 횟수.
    ra   r8   r`   r:   rd   r<    ci_gate_failed_with_stale_reason$current_head_unchanged_since_failureunresolved_thread_countr   tuple[str, ...]failed_job_identifiersr   previous_rerun_attemptsN)r   r   r   r   ri   r   r   r   r   r   r   r     sB     N"&&&***..  .0O0#$S$r   r   c                    | j                   t        k\  ry| j                  }| j                  }| j                  }| j
                  dk(  }t        |xr
 |xr |xr |      S )u  CI rerun 4조건 + cap 미초과 여부 판정 (task-2565 §5 Phase 3.1).

    4조건 (모두 충족 + cap 미초과 시 True):
      1. gemini_fresh_on_current_head: Gemini fresh evidence가 current head에 존재
      2. ci_gate_failed_with_stale_reason: stale reason으로 실패한 CI gate 존재
      3. current_head_unchanged_since_failure: CI 실패 이후 head 변경 없음
      4. unresolved_thread_count == 0: 미해결 thread 없음

    추가 cap: previous_rerun_attempts >= MAX_CI_RERUN_PER_HEAD(=3) 시 False.

    Args:
      inp: CIRerunInput 판정 입력.

    Returns:
      4조건 모두 충족 + cap 미초과 시 True, 하나라도 불충족 시 False.
    Fr   )r   r   r<   r   r   r   rd   rk   s        r   should_rerun_failed_ci_jobsr     sa    $ ""&;;,,E00E44E''1,E3%3E3e44r   c           	     *    d| |t        |      |||ddS )u(  ``anu_v2.ci_rerun_decision.v1`` schema dict 생성 (task-2565 §5 Phase 3).

    박제 필드:
      - ``schema``: "anu_v2.ci_rerun_decision.v1"
      - ``long_polling_used``: 항상 False (long polling 금지 박제)

    Args:
      pr_number: PR 번호.
      current_head_sha: 현재 head SHA.
      failed_jobs: rerun 대상 CI job 식별자 목록.
      rerun_allowed: rerun 허용 여부.
      reason: 허용/차단 이유 문자열.
      attempt_number: 이번 rerun 시도 번호.

    Returns:
      ci_rerun_decision.v1 schema dict.
    zanu_v2.ci_rerun_decision.v1F)r6   r8   r:   failed_jobsrerun_allowedr   attempt_numberrB   )listr8   r:   r   r   r   r   s         r   build_ci_rerun_decisionr     s+    6 0,K(&("	 	r   )rerun_callablec                  | j                   dz   }| j                   t        k\  rLt        | j                  | j                  | j
                  dd| j                    dt         |      }d|d   |dS t        |       sr| j                  sd	}n.| j                  sd
}n| j                  sd}nd| j                   d}t        | j                  | j                  | j
                  d||      }d||dS |8t        | j                  | j                  | j
                  dd|      }d|d   |dS 	  || j                  | j
                         t        | j                  | j                  | j
                  dd| j                   d| d|      }d|d   |dS # t        $ rZ}dt        |      j                   d| }t        | j                  | j                  | j
                  d||      }d||dcY d}~S d}~ww xY w)u  CI rerun 자동 호출 진입점 (adapter pattern, task-2565 §5 Phase 3).

    흐름:
      1. should_rerun_failed_ci_jobs(inp) 호출
      2. cap 초과 → result="CAP_EXCEEDED"
      3. 조건 미충족 (cap 미초과) → result="SKIPPED"
      4. 조건 충족 + rerun_callable=None → result="DECISION_ONLY"
      5. 조건 충족 + rerun_callable 있음 → 호출 후 result="POSTED" 또는 "FAILED"

    Args:
      inp: CIRerunInput 판정 입력.
      rerun_callable: (pr_number, failed_jobs) → dict 형태 callable.
        None이면 DECISION_ONLY 반환 (dry-run 모드).

    Returns:
      dict with keys: result (str), reason (str), decision (dict)
       Fu0   CI rerun cap 초과 — previous_rerun_attempts=z >= r   CAP_EXCEEDEDr   )r}   r   rO   u3   Gemini fresh evidence 미존재 — rerun 건너뜀u9   CI gate 실패가 stale reason 아님 — rerun 건너뜀u3   CI 실패 이후 head 변경됨 — rerun 건너뜀u   unresolved thread 존재 (u   개) — rerun 건너뜀SKIPPEDNTu?   CI rerun 조건 충족 — dry-run 모드 (rerun_callable=None)DECISION_ONLYu   CI rerun 호출 성공 (pr=z
, attempt=r   r~   u   rerun_callable 예외: r   r   )r   r   r   r8   r:   r   r   r<   r   r   r   r   r   r   )rl   r   r   rO   r   r   s         r   auto_rerun_failed_ci_jobsr   7  s?   , 0014N ""&;;*mm 1122EcFaFaEbbfg|f}~)
 %x( 
 	
 's+//JF55PF99JF1#2M2M1NNfgF*mm 1122)
   
 	
 *mm 1122T)
 &x( 
 	

s}}c&@&@A*mm 11220z.IYYZ[)
 x( 
 	

  
*49+=+=*>bF*mm 1122)
  
 	

s    'A%F 	G0AG+%G0+G0c                  N    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   y)MergeReadyInputu  MERGE_READY 6조건 판정 입력 (task-2565 §5 Phase 3.2, context.md §7.5).

    Attributes:
      ci_all_green: CI 11/11 SUCCESS 여부.
      gemini_fresh_on_current_head: latest review commit_id == current head 여부.
      unresolved_thread_count: 미해결 thread 수.
      merge_state_status: mergeStateStatus 값 ("CLEAN" | "BEHIND" | "BLOCKED" | ...).
      effective_diff_matches_expected_files: effective diff == expected_files 여부.
      forbidden_path_count: forbidden path 위반 파일 수 (0 = 허용).
    rd   ci_all_greenr<   ra   r   r`   merge_state_status%effective_diff_matches_expected_filesforbidden_path_countNr   r   r   r   ri   r   r   r   r   r     s-    	 "&&  +//r   r   c                    t        | j                  xrM | j                  xr? | j                  dk(  xr. | j                  dk(  xr | j
                  xr | j                  dk(        S )u  MERGE_READY 6조건 전수 충족 여부 판정 (task-2565 §3.2 + context.md §7.5).

    6조건 (모두 충족 시 True):
      1. ci_all_green: CI 11/11 SUCCESS
      2. gemini_fresh_on_current_head: Gemini fresh evidence on current head
      3. unresolved_thread_count == 0: 미해결 thread 없음
      4. merge_state_status == "CLEAN": mergeStateStatus CLEAN
      5. effective_diff_matches_expected_files: effective diff == expected_files
      6. forbidden_path_count == 0: forbidden path 위반 없음

    Args:
      inp: MergeReadyInput 판정 입력.

    Returns:
      6조건 모두 True면 True, 하나라도 False면 False.
    r   CLEAN)rd   r   r<   r   r   r   r   rs   s    r   is_merge_readyr    su    "  	*,,	*''1,	* ""g-	* 55		*
 $$) r   c                  :    e Zd ZU dZded<   ded<   ded<   ded<   y	)
PreMergeCommitInputu  pre-merge commit 차단 판정 입력 (task-2565 §5 Phase 3.2).

    Attributes:
      merge_ready: MERGE_READY 6조건 충족 여부.
      changed_files: 변경 파일 경로 목록.
      commit_message_hint: commit message prefix 힌트 ("report:", "qc:", "lifecycle:", "evidence:", ...).
      only_touches_report_files: 변경 파일이 비기능 영역(memory/reports/, memory/events/)만인지.
    rd   rU   r   rV   r`   commit_message_hintonly_touches_report_filesNr   r   r   r   r  r    s!     ""##r   r  c                   | j                   j                         j                         }| j                  r#|j	                  d      s|j	                  d      ry|j	                  d      ry| j
                  D cg c]*  }|j	                  d      s|j	                  d      rd|v r|, }}|r"t        |      t        | j
                        k(  ry|j	                  d      ry	| j                  ry| j
                  D cg c]  }d
|v s	d|v sd|v s| }}| j
                  D cg c]  }|j                  d      r||vr| }}|r|sy|ryy	c c}w c c}w c c}w )u  commit 변경 유형 5종 중 매칭 결정 (내부 헬퍼).

    5종 wording 패턴:
      1. "report wording only" — only_touches_report_files & hint starts with "report"
      2. "QC Verdict wording" — hint starts with "qc"
      3. "lifecycle marker wording" — only_touches lifecycle marker dir
      4. "non-functional summary" — only_touches_report_files & generic
      5. "evidence text 보강" — hint starts with "evidence"

    내부적으로 matched change_type을 결정:
      - report_only: 1, 4
      - report_only (qc): 2
      - report_only (lifecycle): 3
      - evidence_required: 5
      - code_fix: 코드/테스트 변경 포함
      - test_fix: 테스트만 변경
    reportzreport:r2   qczmemory/events/zmemory/z/lifecycle/evidencer3   test_z_test.z/tests/z.pyr1   r0   )r  lowerstripr  
startswithrV   lenr   )rl   hintflifecycle_files
test_files
code_filess         r   _detect_change_typer    se   $ ""((*002D $$$//(*CtW`Ga t $$<<()all9.E-[\J\ 	
O  3/3s7H7H3II z"" $$ !..b'Q,(a-S\`aSa!bJb$$::e*!4 	
J 
 * 9  cs   8/E	:EE Ec                \    t        |       }| j                  s|dfS |dk(  ry|dv r|dfS |dfS )u  pre-merge commit 유형 분류 + 허용 여부 결정 (task-2565 §5 Phase 3.2).

    반환 규칙:
      - merge_ready=True + 비기능 wording 패턴 → ("report_only", False) — 차단
      - merge_ready=True + 코드/테스트 변경 포함 → ("code_fix" | "test_fix", True) — 허용
      - merge_ready=False → (change_type, True) — 허용 (merge_ready 아직 아니면 차단 불필요)

    Args:
      inp: PreMergeCommitInput 판정 입력.

    Returns:
      (change_type, pre_merge_commit_allowed) 튜플.
    Tr2   )r2   F)r0   r1   )r  rU   )rl   rW   s     r   classify_pre_merge_commitr  -  sN     &c*K??T"" m#%..T"" r   c                    t        |       \  }}t        | j                  xr
 |dk(  xr |       }t        | j                  t	        | j
                        |||dS )u>  pre-merge commit 차단 decision dict 생성 (task-2565 §5 Phase 3.2).

    schema: PRE_MERGE_COMMIT_DECISION_SCHEMA
    merge_ready=True + report_only 시 redirect_to_post_merge_evidence=True.

    Args:
      inp: PreMergeCommitInput 판정 입력.

    Returns:
      pre_merge_commit_decision.v1 schema dict.
    r2   rT   )r  rd   rU   r   r   rV   )rl   rW   allowedredirects       r   build_pre_merge_block_decisionr  N  s_     5S9KCOOT}(DTWUH 3c//0"$++3 r   c                    | j                  dd      }| j                  dg       }|D cg c]  }d|v sd|v s| }}|rd}n#|dk(  r|D cg c]	  }d|v s| }}|rd	}nd
}nd
}|t        |      |dS c c}w c c}w )u  차단된 pre-merge commit decision → post-merge marker용 payload 생성 (task-2565 §5 Phase 3).

    차단된 report_only commit을 post-merge 단계로 redirect하기 위한 payload.

    redirect_to 결정:
      - change_type="report_only" + original hint 기반:
        - "lifecycle marker"가 changed_files에 있으면 "lifecycle marker"
        - 그 외 "post-merge smoke" 또는 "reconcile evidence"

    Args:
      blocked_decision: build_pre_merge_block_decision 반환 dict.

    Returns:
      dict with keys:
        - original_change_type: str
        - original_changed_files: list[str]
        - redirect_to: "post-merge smoke" | "reconcile evidence" | "lifecycle marker"
    rW   rz   rV   	lifecycleeventszlifecycle markerr2   reportszreconcile evidencezpost-merge smoke)original_change_typeoriginal_changed_filesredirect_to)r   r   )blocked_decisionrW   rV   r  r  r#  report_filess          r   $post_merge_evidence_redirect_payloadr&  j  s    & #&&}b9K$(("=M #0UQ;!3CxST}qUOU(		%#0CaINCC.K,K( !,"&}"5"  V
 Ds   A4A4	A9A9)r   r   r   r   r   rF   rR   rZ   r\   r   r_   rq   rt   rv   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r&  )r7   r`   r8   ra   r9   r`   r:   r`   r;   r`   r<   rd   r=   r`   r>   r`   r?   rd   r@   r`   rC   rb   rD   rd   rE   r`   returndict[str, Any])rO   r(  r'  None)rU   rd   rV   z	list[str]rW   r`   rX   rd   rY   rd   r'  r(  )rl   r_   r'  rd   )rl   r_   r'  r   )rl   r_   r   z!Callable[[int, str], dict] | Noner'  dict)
r7   r`   r   r   rO   r*  r   Path | Noner'  z
list[Path])r   r*  r   r+  r'  r   )N)r   r`   r   zdict[str, Any] | Noner   r+  r'  r   )rl   r   r'  rd   )r8   ra   r:   r`   r   r   r   rd   r   r`   r   ra   r'  r(  )rl   r   r   z-Callable[[int, tuple[str, ...]], dict] | Noner'  r(  )rl   r   r'  rd   )rl   r  r'  r`   )rl   r  r'  ztuple[str, bool])rl   r  r'  r(  )r$  r(  r'  r(  )Cr   
__future__r   r   r   r   dataclassesr   r   enumr   pathlibr   typingr   r	   r
   anu_v2.polling_policyr   r   ri   r   r   r   r   r   r&   r)   r/   r4   rF   rR   rZ   r\   r_   rq   rt   rv   r   r   r   r   r   r   r   r    r!   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r&  __all__r   r   r   <module>r3     sZ  . #  	  (   ' '
 >
 -O z N/T  * T
jj jH H8 .7 8 . *  09 : 0 ,  09 : 0 ,  -6 7 - ) :: : 	:
 : !: #': : : !: : ": : : :zH
Z  	
 # &* <'
Z $7 7 785,	N;<, ;?R
	R
 8R
 
	R
r 3399744::988>>6228803399(55;;3, (  +R Z Q G #??? ?
 ? ?H 'O  N C #,, , 
	,b /
K / g
 &*2! #	2!2!"2! 	2!
 
2!n %& z % c
 $% % %05:## # !	#
 # # # #R EIp
	p
 Bp
 	p
j $  (: $$ $ $ ;|B8(Z!r   