
    j                    .   U d Z ddl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 ddlmZ ddlmZmZmZmZ dd	lmZmZmZmZmZmZmZmZ dd
lmZmZm Z m!Z!m"Z"m#Z# ddl$m%Z%m&Z&m'Z' ddl(m)Z) ddl*m+Z+m,Z,m-Z-m.Z. ddl/m0Z0m1Z1m2Z2m3Z3m4Z4 ddl5m6Z6m7Z7m8Z8m9Z9  ejt                  e;      Z<dZ=de>d<   dZ?de>d<   dZ@de>d<   dZAde>d<   dZBde>d<   dZCde>d<   e,eDeEeFeGeHeIfZJde>d<   dCdZKdDd ZLd!ZMde>d"<   d#ZNde>d$<   d%ZOde>d&<   d'ZPde>d(<   d)ZQde>d*<   d+ZRde>d,<   d-ZSde>d.<   d/ZTde>d0<   d1ZUde>d2<   d3ZVde>d4<    e
d56       G d7 d8             ZW e
d56       G d9 d:             ZX G d; d<      ZYd=ddd>	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dEd?ZZdFd@Z[dGdAZ\g dBZ]y)Hu  anu_v2.executor_scheduler — 자동 entry point (task-2556 §1 / §5 / §6 / §7 / §8 / §10 / §12).

회장 §명시 2026-05-12 KST (task-2556 본질):
  PR #106 → OWNER_TRIGGER_ONLY_CAPABILITY runner 는 main 반영. 단 PR #107 이 실증한 갭:
    - idle PR 을 자동 감지해 runner 를 호출하는 daemon/scheduler entry point 부재.
    - 봇 session 종료되면 ``FIRST_GEMINI_TRIGGER_MISSING`` / ``GEMINI_STALE_ON_HEAD``
      자동 처리 안 됨.
  본 task = 자동 entry point (executor scheduler) 구현. capability 자동 활성화 완성.

본 모듈 책임 (회장 §1 / §5 / §6 / §7 / §8 / §10 / §12):
  §1  OPEN PR idle scan — ``gh pr list --state open`` 결과를 받아 정기 scan.
  §5  OWNER_TRIGGER_REQUIRED decision write — ``emit_owner_trigger_decision`` 호출.
  §6  ``owner_trigger_only.trigger_gemini_review()`` runner 자동 호출.
  §7  decision.json / audit.jsonl / requested/posted/failed marker 박제.
  §8  scheduled/event-driven recheck (회장 chat 노출 0).
  §10 duplicate same-head dedupe — fcntl.flock atomic, 이미 trigger 된 head 재호출 차단.
  §12 bot session 종료 후 재진입 — state persisted markers 기반 scheduler 가 재진입.

본 모듈 NOT 책임:
  - HTTP call 직접 수행 (owner_trigger_only 가 함).
  - merge 실행 (merge_queue_executor 가 함).
  - 회장 chat 알림 (정책상 노출 0 — scheduler 는 silent).

부수효과 (제한):
  - decision_dir 에 owner_trigger_decision.json / marker 파일 생성.
  - scheduler lock 파일 fcntl.flock (same-head dedupe).
  - audit JSONL append (owner_trigger_audit 가 함).

one-way isolation: anu_v2/* 만 import. 외부 (utils/dispatch/scripts/dashboard) 의존성 0.
    )annotationsN)contextmanager)	dataclass)datetimetimezone)Path)CallableFinalIteratorSequence)IdlePRDiagnoserIdlePRDiagnosisIdlePRSnapshot"STATE_FIRST_GEMINI_TRIGGER_MISSINGSTATE_FIRST_TRIGGER_PENDINGSTATE_GEMINI_FRESH_ON_HEADSTATE_GEMINI_STALE_ON_HEADSTATE_WITHIN_GRACE_PERIOD)AUTO_MERGE_ALLOWEDGEMINI_FRESH_DETECTEDMergeQueueExecutorOWNER_TRIGGER_FAILEDOWNER_TRIGGER_POSTEDPRMeta)RESULT_DEDUPEDRESULT_FAILEDRESULT_POSTED)DecisionInvalidError)OwnerTriggerOnlyTokenBoundaryViolationassert_scheduler_token_boundaryinvoke_from_scheduler)BotSessionExitRequiredPollingStateadvance_recheck!assert_first_timeout_not_exceededmust_exit_now)SecondReviewInputauto_trigger_owner_reviewdetermine_stateemit_phase2_markersz%memory/events/executor_scheduler.lockz
Final[str]SCHEDULER_LOCK_REL_PATHz,memory/events/executor_scheduler_audit.jsonlSCHEDULER_AUDIT_REL_PATHzanu_v2.executor_scheduler.v1SCHEDULER_AUDIT_SCHEMAz)anu_v2.executor_scheduler.pr_exception.v1PR_EXCEPTION_AUDIT_SCHEMAPR_EXCEPTION_ISOLATEDACTION_PR_EXCEPTION_ISOLATEDPR_EXCEPTION_CRITICAL_ESCALATED&ACTION_PR_EXCEPTION_CRITICAL_ESCALATEDz&Final[tuple[type[BaseException], ...]]_CRITICAL_EXCEPTIONSc                "    t        | t              S )uL   주어진 예외가 critical (escalation marker 박제) 대상인지 판정.)
isinstancer4   )excs    ;/home/jay/workspace/scripts/../anu_v2/executor_scheduler.py_is_critical_exceptionr9      s    c/00    c                   t        |       j                  t        |       j                  t        |       dd d}| j                  }|}|}|j
                  |j
                  }|j
                  |j                  j                  }t        |j                        j                  |d<   |j                  |d<   |j                  |d<   |S )uS   exception 한 줄 summary (decision/audit 기록용). traceback frame 0 만 박제.Ni   )typemodulemessageorigin_filenameorigin_functionorigin_lineno)r<   __name__
__module__str__traceback__tb_nexttb_framef_coder   co_filenamenameco_name	tb_lineno)r7   summarytblastcodes        r8   _summarize_exceptionrQ      s     S	""s)&&s8DS>G
 
		B	~ll&<<D ll&}}##%)$*:*:%;%@%@!"%)\\!"#'>> Nr:   OWNER_TRIGGER_DISPATCHEDACTION_OWNER_TRIGGER_DISPATCHEDOWNER_TRIGGER_DEDUPEDACTION_OWNER_TRIGGER_DEDUPEDr   ACTION_OWNER_TRIGGER_FAILEDFRESH_GEMINI_AUTO_RESUMEACTION_FRESH_RESUMEWITHIN_GRACE_PERIOD_SKIPACTION_WITHIN_GRACECI_FAILED_SKIPACTION_CI_FAILED_SKIPMISSING_TASK_ID_SKIPACTION_MISSING_TASK_ID_SKIPSAME_HEAD_DEDUPEDACTION_SAME_HEAD_DEDUPEDFIRST_TRIGGER_PENDING_SKIP!ACTION_FIRST_TRIGGER_PENDING_SKIPowner_trigger_fast_pathDISPATCH_DECISION_FAST_PATH_KEYT)frozenc                      e Zd ZU dZ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<   d	Zded<   d	Zded<   ddZ	y)SchedulerPRActionu/   단일 PR 에 대한 scheduler 결정 + 결과.int	pr_numberrD   task_idhead_shastateaction runner_resultdecision_pathmarker_pathreasonc                    | j                   t        t        t        t        t
        t        t        t        t        t        t        ddhvrt        d| j                   d      y )NDIAGNOSIS_ONLYUNKNOWN_STATE_SKIPzaction z not in allowed set)rm   rS   rU   rV   rX   rZ   r\   r^   r`   rb   r1   r3   
ValueErrorselfs    r8   __post_init__zSchedulerPRAction.__post_init__   sZ    ;;+('!'$-(2 
 
 wt{{o5HIJJ
r:   N)returnNone)
rB   rC   __qualname____doc____annotations__ro   rp   rq   rr   ry    r:   r8   rg   rg      sL    9NLMJKM3M3KFCKr:   rg   c                      e Zd ZU dZded<   ded<   ded<   dZded	<   dZded
<   dZded<   dZded<   dZ	ded<   dZ
ded<   y)SchedulerCycleResultu  run_one_cycle 의 종합 결과 (모든 PR 합산).

    각 PR 별 SchedulerPRAction 리스트 + chat-noise-free 어셀션 (chat_notifications=0).

    task-2560 FUC-4 per-PR isolation:
      - ``pr_exceptions_isolated`` — non-critical exception 발생 PR 수 (cycle 유지).
      - ``pr_exceptions_critical_escalated`` — critical 7 분류 exception 발생 PR 수
        (cycle 유지 + escalation marker 박제).
      - ``cycle_crashed`` — cycle 자체가 중단됐는지 (per-PR isolation 성공 시 False).
    rD   cycle_started_atcycle_finished_atztuple[SchedulerPRAction, ...]
pr_actionsr   rh   chat_notificationsrechecks_doneTboolbot_should_exitpr_exceptions_isolated pr_exceptions_critical_escalatedFcycle_crashedN)rB   rC   r|   r}   r~   r   r   r   r   r   r   r   r:   r8   r   r      sZ    	 --M3 OT "#C#,-$c-M4r:   r   c                  T   e Zd ZdZddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZedd       Zedd       Zedd       Z		 	 	 	 	 	 ddZ
	 	 	 	 	 	 dd	Z	 	 	 	 	 	 dd
Zdddd	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZddZ	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZdd	 	 	 	 	 ddZy)ExecutorScheduleruh  OPEN PR idle scan + 자동 entry point. 회장 §1/§5/§6/§7/§8/§10/§12 1:1.

    구성 (DI):
      - ``workspace_root``: anu_v2 workspace 루트 (decision/marker/audit 경로 계산).
      - ``decision_dir``: marker / decision.json 디렉토리.
      - ``snapshot_provider``: ``Callable[[], Sequence[IdlePRSnapshot]]`` —
        ``gh pr list --state open --json ...`` 결과를 정규화해 반환. 본 모듈은 외부 명령
        실행을 직접 안 한다 (one-way isolation).
      - ``owner_trigger``: ``OwnerTriggerOnly`` 인스턴스. 본 클래스는 본 runner 를
        ``invoke_from_scheduler`` 어댑터로 호출.
      - ``merge_executor``: ``MergeQueueExecutor`` 인스턴스. fresh evidence 도착 시
        ``auto_resume_after_fresh_evidence`` 를 호출하기 위함.
      - ``owner``, ``repo``: GitHub owner / repo.
      - ``diagnoser``: 선택. 미주입 시 default ``IdlePRDiagnoser``.

    설계 원칙:
      - **chat_notifications == 0**: 본 모듈은 telegram / cokacdir / slack 어떤 외부
        채널에도 알림을 보내지 않는다 (회장 §8 1:1).
      - **long polling 0**: 본 모듈은 자체 sleep loop 가 없다. ``run_one_cycle`` 은
        한 번만 scan + dispatch 후 즉시 반환. 재진입은 외부 cron/webhook 책임 (§8).
      - **same-head dedupe**: ``fcntl.flock`` 기반 atomic. 동일 (pr, head) 가 활성
        trigger 를 가진 경우 SchedulerPRAction.action == SAME_HEAD_DEDUPED.
      - **state persisted markers**: 본 모듈의 모든 결정은 audit/marker 로 박제되어
        다음 cycle 이 재진입 시 상태 복원 가능.
    N)	diagnoserclockc       	           |t        d      t        |t              st        d      t        |t              st        d      t        |t
              r|rd|v rt        d      t        |t
              r|rd|v rt        d      t        |      j                         | _	        t        |      j                         | _
        || _        || _        || _        || _        || _        ||n	t!               | _        |	|	| _        y t$        | _        y )Nz"snapshot_provider must be injectedz/owner_trigger must be OwnerTriggerOnly instancez2merge_executor must be MergeQueueExecutor instance/z*owner must be non-empty string without '/'z)repo must be non-empty string without '/')NotImplementedErrorr6   r   	TypeErrorr   rD   rv   r   resolve_workspace_root_decision_dir_snapshot_provider_owner_trigger_merge_executor_owner_repor   
_diagnoser_now_iso_clock)
rx   workspace_rootdecision_dirsnapshot_providerowner_triggermerge_executorownerrepor   r   s
             r8   __init__zExecutorScheduler.__init__  s     $%&JKK-)9:MNN.*<=PQQ%%UcUlIJJ$$DC4KHII#N3;;=!,/779"3+-
'0'<)/BS$0ehr:   c                (    | j                   t        z  S )u.   scheduler same-head dedupe lock 파일 경로.)r   r,   rw   s    r8   	lock_pathzExecutorScheduler.lock_path1  s     ##&===r:   c                (    | j                   t        z  S )u5   scheduler audit JSONL 경로 (cycle / action 박제).)r   r-   rw   s    r8   
audit_pathzExecutorScheduler.audit_path6  s     ##&>>>r:   c              #    K   | j                   j                  j                  dd       t        | j                   dd      5 }t	        j
                  |j                         t        j                         	 d t	        j
                  |j                         t        j                         	 ddd       y# t	        j
                  |j                         t        j                         w xY w# 1 sw Y   yxY ww)u,  fcntl LOCK_EX 기반 atomic scheduler lock (회장 §10).

        동시에 다른 scheduler instance 가 진입하면 후속 instance 는 본 lock 에서
        block 된다 (POSIX advisory). 본 lock 은 sidecar lock 파일에 잡혀 audit JSONL
        본 파일 lock 과 분리된다.
        Tparentsexist_okautf-8encodingN)	r   parentmkdiropenfcntlflockfilenoLOCK_EXLOCK_UN)rx   lock_fhs     r8   _scheduler_lockz!ExecutorScheduler._scheduler_lock=  s      	##D4#@$..#8 	=GKK(%--8=GNN,emm<	= 	=
 GNN,emm<	= 	=s6   A C93C-6B6:2C--	C964C**C--C62C9c               T    | j                   j                  }|j                  ||      S )u   (pr, head) 에 대해 활성 owner trigger 가 이미 있는지 audit 으로 확인.

        owner_trigger_audit 의 bounded reverse scan 을 활용 (capability 재사용).
        )prhead)r   _audit_has_active_trigger)rx   ri   rk   audits       r8   _has_active_trigger_for_headz.ExecutorScheduler._has_active_trigger_for_headM  s+     ##**((IH(EEr:   c               ~   | j                  |j                  |j                        rBt        |j                  |j                  |j                  |j
                  t        t        d      S t        |      }| j                  j                  |j                  || j                         | j                  |j                   dz  }	 t        | j                  || j                  | j                  |j                        }|t.        k(  r| j                  j#                  |j                  |t0        | j                        }t        |j                  |j                  |j                  |j
                  t2        t.        t-        |      t-        |      |j4                  	      S |t        k(  rLt        |j                  |j                  |j                  |j
                  t6        t        t-        |      d      S | j                  j#                  |j                  |t$        | j                  d|i
      }t        |j                  |j                  |j                  |j
                  t(        |t-        |      t-        |      d| 	      S # t         $ r}| j                  j#                  |j                  |t$        | j                  dt'        |dd	      i
      }t        |j                  |j                  |j                  |j
                  t(        t*        t-        |      t-        |      dt'        |dd	       	      cY d}~S d}~ww xY w)u5  OWNER_TRIGGER_REQUIRED 진단 결과를 owner_trigger runner 로 dispatch.

        흐름 (회장 §5 / §6 / §7 1:1):
          1. same-head dedupe 검사 (audit 기반).
          2. PRMeta 구성 (snapshot → merge_queue_executor 호환).
          3. ``merge_executor.emit_owner_trigger_decision`` 호출 — decision.json + requested marker.
          4. ``invoke_from_scheduler(runner, ...)`` 어댑터로 OwnerTriggerOnly 호출.
          5. 결과 (POSTED|DEDUPED|FAILED|PENDING) 에 따라 marker 기록.
          6. SchedulerPRAction 반환.
        )ri   rk   z8audit shows active POSTED|PENDING trigger for (pr, head))ri   rj   rk   rl   rm   ro   rr   )rj   r   r   z.owner_trigger_decision.json)rp   r   r   current_head_actualdecision_invalid_coderP   rn   )rj   r   outcome_coder   extrazdecision invalid: )	ri   rj   rk   rl   rm   ro   rp   rq   rr   N)rj   r   r   r   z,runner returned DEDUPED (atomic dedupe race))ri   rj   rk   rl   rm   ro   rp   rr   ro   zrunner result )r   ri   rk   rg   rj   rl   r`   r   _snapshot_to_pr_metar   emit_owner_trigger_decisionr   r"   r   r   r   r   record_owner_trigger_outcomer   getattrrV   r   rD   r   r   rS   rr   rU   )rx   diagsnapshotpr_metarp   ro   r7   rq   s           r8   _dispatch_owner_triggerz)ExecutorScheduler._dispatch_owner_triggerY  s   " ,,nnt}} - 
 %..jj/,Q  'x0 	88LL++ 	9 	
 DLL>1M!NN 	
	1##+kkZZ$(MMM: M)..KK1!//	 L K %..jj6+!-0,{{
 
 N*$..jj3,!-0E	 	 **GGLL-++"M2 H 
 !nnLL]]**.'m,K(#M?3

 
	
w $ 	..KK1!//.VR0HI L K %..jj2+!-0,+GC,D+EF
 
	s   8J   	L<	B(L71L<7L<c          
        t        |      }| j                  j                  |j                  ||j                  | j
                        }d}|j                  t        k(  r>|j                  t        k(  r+|j                  xs i }t        |j                  dd            }t        |j                  |j                  |j                  |j                   t"        d||j                        S )uP   gemini fresh on head 진단 → merge_executor.auto_resume_after_fresh_evidence.)rj   r   latest_gemini_review_commit_idr   rn   rq   )ri   rj   rk   rl   rm   rp   rq   rr   )r   r    auto_resume_after_fresh_evidencerj   latest_gemini_commit_idr   decisionr   rr   r   r   rD   getrg   ri   rk   rl   rX   )rx   r   r   r   outcome
marker_strr   s          r8   _handle_fresh_evidencez(ExecutorScheduler._handle_fresh_evidence  s     'x0&&GGLL+/+G+G++	 H 
 
 22"77MM'REUYY}b9:J nnLL]]**&">>	
 		
r:   )envnowcycle_polling_statec                  t        |       ||n	t               }t        |      r&t        d|j                   d|j
                   d      | j                         }g }d}d}| j                         5  t        | j                               }	| j                  j                  |	|      }
|	D ci c]  }|j                  | }}|
D ]  }|j                  |j                        }|I|j                  t!        |j                  |j"                  |j$                  |j&                  dd	             i| j)                  |||
      }|j                  |       |j*                  t,        k(  r|dz  }n|j*                  t.        k(  r|dz  }| j1                  ||        	 ddd       | j                         }t3        ||t5        |      d|j                  t        |      ||d	      S c c}w # 1 sw Y   NxY w)u  단일 scan + dispatch + exit. 회장 §1/§2/§5~§8/§10/§12 1:1.

        본 메서드는 **단일 cycle 만 실행** 한다. while True 루프 0 — long polling 0
        (회장 §9). 다음 cycle 은 외부 cron / webhook 에서 본 메서드를 재호출.

        흐름:
          1. token boundary 검증 (env 주입 — scheduler 가 OWNER token 만 들고 있음).
          2. polling state 검증 (must_exit_now 시 즉시 종료).
          3. ``snapshot_provider()`` 호출 → OPEN PR snapshot 목록.
          4. diagnoser.diagnose_all 일괄 진단.
          5. 각 PR 진단 결과에 따라 action 분기:
             - WITHIN_GRACE_PERIOD → ACTION_WITHIN_GRACE (skip, marker 없음)
             - MISSING_TASK_ID    → ACTION_MISSING_TASK_ID_SKIP
             - CI_FAILED          → ACTION_CI_FAILED_SKIP
             - FIRST_GEMINI_TRIGGER_MISSING | GEMINI_STALE_ON_HEAD → dispatch
             - GEMINI_FRESH_ON_HEAD → auto-resume marker
          6. SchedulerCycleResult 반환.

        Args:
          env: scheduler env dict. ``OWNER_GEMINI_TRIGGER_TOKEN`` 필수.
          now: 진단용 ISO UTC (테스트 시 결정성 보장). 미주입 시 ``datetime.now(UTC)``.
          cycle_polling_state: 현재 cycle 의 polling state. 미주입 시 fresh state.

        Returns:
          SchedulerCycleResult.

        Raises:
          TokenBoundaryViolation: env 가 boundary 위반.
          BotSessionExitRequired: polling 정책상 즉시 exit.
        Nz+polling state requires exit (rechecks_done=z
, elapsed=zs)r   )r   ru   zsnapshot missingri   rj   rk   rl   rm   rr   )r   r   cycle_started   )r   rm   F)	r   r   r   r   r   r   r   r   r   )r!   r$   r'   r#   r   elapsed_secondsr   r   listr   r   diagnose_allnumberr   ri   appendrg   rj   rk   rl   _safe_handle_single_diagnosisrm   r1   r3   _append_auditr   tuple)rx   r   r   r   rl   r   actionsisolated_countcritical_count	snapshots	diagnosess
snap_by_prr   snaprm   cycle_finisheds                    r8   run_one_cyclezExecutorScheduler.run_one_cycle  s   L 	(,':'F#LN(=e>Q>Q=R S 0015 
 +-!!# 	OT4467I44YC4HI KT4TQQXXq[4TJ4T! O!~~dnn5<NN)&*nn$(LL%)]]"&**#7#5	  ;;M <  v&==$@@"a'N]]&LL"a'N""v"N1O	OB #*,W~ --)%0#1-;

 
	
; 5U	O 	Os   1;G&,G! CG&!G&&G/c          	     >   |j                   t        k(  rGt        |j                  |j                  |j
                  |j                   t        |j                        S |j                   dk(  rGt        |j                  |j                  |j
                  |j                   t        |j                        S |j                   dk(  rGt        |j                  |j                  |j
                  |j                   t        |j                        S |j                   t        k(  r| j                  ||      S |j                   t        k(  rq| j                  |      }|r| j                  ||      S t        |j                  |j                  |j
                  |j                   t        |j                   d      S |j                   t         t"        fv r| j                  ||      S t        |j                  |j                  |j
                  |j                   d|j                        S )u*   단일 PR 진단 결과 → action 분기.r   MISSING_TASK_ID	CI_FAILEDr   r   )r   ui    — fast_path=false, owner trigger 보류 (FIRST_TIMEOUT_SECONDS 경과 후 MISSING 확정 시 dispatch)ru   )rl   r   rg   ri   rj   rk   rZ   rr   r^   r\   r   r   r   _load_fast_path_flagr   rb   r   r   )rx   r   r   	fast_paths       r8   _handle_single_diagnosisz*ExecutorScheduler._handle_single_diagnosisX  s    ::22$..jj*{{  ::**$..jj2{{  ::$$..jj,{{  ::33..D8.LL::44 11t1<I333QQ$..jj8{{m $U V
 
 ::.&
 
 //TH/MM nnLL]]**';;
 	
r:   c               ^   	 g }|j                   r+|j                  | j                  |j                    dz         |j                  | j                  d|j                   dz         |D ]e  }|j	                         s	 t        j                  |j                  d            }t        |t              s y|j                  t              }|du c S  	 y# t
        j                  t        f$ r Y  yw xY w# t        $ r0 t        j!                  d|j                  |j                   d       Y yw xY w)	u  ``dispatch_decision.owner_trigger_fast_path`` flag 를 로드.

        task-2563 §1 1:1: FIRST_TRIGGER_PENDING 상태에서 owner trigger 를 조기 dispatch 하려면
        반드시 dispatch_decision JSON 에 ``owner_trigger_fast_path: true`` 가 명시되어야 한다.

        loader 동작 (fail-closed):
          1. ``decision_dir / "{task_id}.dispatch-decision.json"`` 경로 확인.
          2. 없거나 JSON 파싱 실패 → False (보수적).
          3. 키 누락 / non-bool / False → False.
          4. ``True`` (Python bool) → True 반환.

        본 loader 는 외부 dispatch_decision 작성자 (회장 / dispatcher) 가 명시적으로 fast_path=true
        를 박제해야만 활성화되도록 설계되어 있다.
        z.dispatch-decision.jsonpr-r   r   FTuH   fast_path flag load failed for pr=%s task_id=%s — fail-closed (False).)exc_info)rj   r   r   ri   existsjsonloads	read_textJSONDecodeErrorOSErrorr6   dictr   rd   	Exceptionloggerwarning)rx   r   
candidatespathdatavalues         r8   r   z&ExecutorScheduler._load_fast_path_flag  s$   	J||!!$"4"4$,,G^7_"_`""s4>>*::Q%RR # 
%{{}!::dnngn&FGD "$- !@A}$
%&  ,,g6 ! !  	NNZ	   	sB   A;C3 >%C#C3 5C3 C3 C0,C3 /C00C3 36D,+D,c                  	 | j                  ||      S # t        $ r  t        $ r}t        |      }t	        |      }	 | j                  ||||      }n# t        $ r d}Y nw xY w|rt        }d|d    d|d    d}	nt        }d	|d    d|d    d
}	t        |j                  |j                  |j                  |j                  ||d   ||	      cY d}~S d}~ww xY w)u  ``_handle_single_diagnosis`` 를 per-PR try/except 로 감싼다 (회장 §명시 본질 1).

        예외 분류:
          - ``BotSessionExitRequired`` / ``KeyboardInterrupt`` / ``SystemExit`` /
            ``GeneratorExit`` → 그대로 전파 (cycle 정상 종료 신호).
          - critical 7 분류 → cycle 유지하되 ESCALATED marker 박제.
          - 그 외 (Exception) → cycle 유지 + FAILED marker 박제, 다음 PR 진행.

        markers / audit:
          - decision_dir 에 ``{task_id}.pr-{pr}.exception.json`` (exception summary).
          - decision_dir 에 ``{task_id}.pr-{pr}.failed`` 또는
            ``{task_id}.pr-{pr}.critical-escalated`` marker.
          - scheduler audit JSONL 에 동일 record 박제 (호출자 ``run_one_cycle`` 책임).
        r   )r   rM   criticalr   rn   zcritical exception r<   : r>   u*    — cycle 유지, ESCALATED marker 박제zisolated exception u'    — cycle 유지, FAILED marker 박제)ri   rj   rk   rl   rm   ro   rq   rr   N)r   r#   r  r9   rQ   _record_pr_exception_markerr3   r1   rg   ri   rj   rk   rl   )
rx   r   r   r   r7   r  rM   rq   action_coderr   s
             r8   r   z/ExecutorScheduler._safe_handle_single_diagnosis  s    2%	00dX0NN% 	 !	-c2H*3/G!">>#%"/	 ?   ! !D)'&/):"y)**TV 
 ;)'&/):"y)**QS  %..jj"%fo'	 	1!	s>    CCACA"C!A""A-CCCc               "   | j                   }|j                  dd       |rdnd}|j                  xs d|j                   }|| d|j                   dz  }|| d|j                   d| z  }	t        | j                         ||j                  |j                  |j                  |j                  ||rt        nt        || j                  | j                  d	d
}
t        |dd      5 }t        j                  |j                         t        j                          	 |j#                  t%        j&                  |
ddd             |j)                          t+        j,                  |j                                t        j                  |j                         t        j.                         	 ddd       |	j1                  d       t3        |	      S # t        j                  |j                         t        j.                         w xY w# 1 sw Y   ]xY w)u8  exception summary 를 decision/audit 박제. marker 경로 반환.

        파일 두 종 생성:
          1. ``{task_id}.pr-{pr}.exception.json`` — 전체 summary (read-only audit).
          2. ``{task_id}.pr-{pr}.{failed|critical-escalated}`` — 0-byte marker
             (lifecycle 감시용).
        Tr   zcritical-escalatedfailedr   z.pr-z.exception.json.r   )schematsr   ri   rj   rk   rl   r  rm   exception_summaryr   r   r   wr   r   F   )ensure_ascii	sort_keysindentN)r   )r   r   rj   ri   r/   r   rk   rl   r3   r1   r   r   r   r   r   r   r   writer   dumpsflushosfsyncr   touchrD   )rx   r   rM   r  r   r   suffix	task_partsummary_pathrq   recordfhs               r8   r  z-ExecutorScheduler._record_pr_exception_marker  s    ))4$7)1%xLL:c$..)9$:	#47G&WW"	{$t~~6Fax%PP/++- -||ZZ   71!([[JJ"##
& ,g6 	8"KK		U]]38F$WXYZ
%BIIK7	8 	4(; BIIK7	8 	8s%   '3HAG62H4HHHc                  | j                   j                  j                  dd       t        | j	                         ||j
                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  | j                  | j                  dd}t!        | j                   dd      5 }t#        j$                  |j'                         t"        j(                         	 |j+                  t-        j.                  |dd	      d
z          |j1                          t3        j4                  |j'                                t#        j$                  |j'                         t"        j6                         	 ddd       y# t#        j$                  |j'                         t"        j6                         w xY w# 1 sw Y   yxY w)uW   scheduler audit JSONL 한 줄 append. 다음 cycle 재진입 시 history 참조 가능.Tr   r   )r  r  r   ri   rj   rk   rl   rm   ro   rp   rq   rr   r   r   r   r   r   r   F)r  r  
N)r   r   r   r.   r   ri   rj   rk   rl   rm   ro   rp   rq   rr   r   r   r   r   r   r   r   r  r   r  r  r  r  r   )rx   r   rm   r$  r%  s        r8   r   zExecutorScheduler._append_audit@  sH    	$$TD$A,++- -))~~\\mm#11#11!--mm[[JJ"#
" $//39 	8RKK		U]]38F$ORVVW
%BIIK7	8 	8 BIIK7	8 	8s%   3GAF#2G4GGG)r   c               N    t        |       ||nt        |      }t        |      S )u  trigger 후 ``elapsed_seconds`` 경과 시 recheck 1 회 등록. chat 노출 0.

        본 함수는 polling 정책을 코드 게이트로 강제:
          - elapsed_seconds <= FIRST_TIMEOUT_SECONDS 검증
          - rechecks_done < MAX_RECHECKS 검증
          - 다음 PollingState 반환 (rechecks_done += 1)

        호출자 (scheduler) 는 본 함수가 반환한 state 를 다음 cycle 에 주입.
        BotSessionExitRequired 발생 시 봇은 즉시 process exit, 다음 cycle 은 외부 cron 에서.
        )r   )r&   r$   r%   )rx   r   r   rl   s       r8   schedule_recheckz"ExecutorScheduler.schedule_recheckd  s/      	*/:':'F#L+M
 u%%r:   )r   
str | Pathr   r*  r   z&Callable[[], Sequence[IdlePRSnapshot]]r   r   r   r   r   rD   r   rD   r   zIdlePRDiagnoser | Noner   zCallable[[], str] | Nonerz   r{   )rz   r   )rz   zIterator[None])ri   rh   rk   rD   rz   r   )r   r   r   r   rz   rg   )r   zdict | Noner   
str | Noner   PollingState | Nonerz   r   )r   r   rz   r   )r   r   r   r   r   rD   rz   rg   )
r   r   rM   r  r  r   r   rD   rz   rD   )r   rD   rm   rg   rz   r{   )r   rh   r   r,  rz   r$   )rB   rC   r|   r}   r   propertyr   r   r   r   r   r   r   r   r   r   r   r  r   r)  r   r:   r8   r   r      sB   H -1*.? #? !	?
 B? (? +? ? ? *? (? 
?F > > ? ? = =FF+.F	Fx
 x
 !	x

 
x
x
 
 !	

 

J  37_
 _
 	_

 1_
 
_
BA
 A
 !	A

 
A
J*\> > !	>
 > 
>@1  1  	1 
 1  1  
1 j 8  8 "	 8
 
 8P 48	& & 1	&
 
&r:   r   r   )owner_trigger_audit_entriestrigger_callabler   c                    	 t        | |||||||||	
      }t        |      }t        ||
      }t        | |||       |S # t        $ r/}ddt        |      j                   d| d| d| d	cY d
}~S d
}~ww xY w)u  Phase 2 second-review hook — follow-up commit 감지 후 자동 호출 진입점.

    task-2565 §5 Phase 2: stale detection + owner trigger 자동 호출.
    기존 경로 회귀 방지: try/except로 감싸 실패 시 빈 dict 반환.

    Args:
      task_id: 관련 task ID.
      pr_number: PR 번호.
      old_head_sha: follow-up commit 이전 head SHA.
      current_head_sha: 현재 head SHA.
      latest_gemini_commit_id: 최신 Gemini review commit_id.
      ci_gate_failure_reason: CI gate 실패 이유.
      unresolved_thread_outdated: unresolved thread OUTDATED 여부.
      follow_up_commit_detected: follow-up commit 감지 여부.
      elapsed_since_follow_up_seconds: follow-up 경과 초.
      owner_trigger_audit_entries: 기존 audit entries (dedupe 검사용).
      trigger_callable: (pr_number, head_sha) → dict 호출 가능. None=dry-run.
      decision_dir: marker 저장 디렉토리. None=MARKER_DIR.

    Returns:
      auto_trigger_owner_review 결과 dict. 예외 발생 시 {"result": "HOOK_ERROR", ...}.
    )
rj   ri   old_head_shacurrent_head_shar   ci_gate_failure_reasonunresolved_thread_outdatedfollow_up_commit_detectedelapsed_since_follow_up_secondsr.  )r/  )
marker_dir
HOOK_ERRORu   phase2 hook 예외: r  F+)resultrr   	triggered
dedupe_keyN)r(   r*   r)   r+   r  r<   rB   )rj   ri   r1  r2  r   r3  r4  r5  r6  r.  r/  r   inprl   r   r7   s                   r8    invoke_phase2_second_review_hookr>    s    J
%-$;#9'A&?,K(C
  $,SCSTGUHN 
",T#Y-?-?,@3%H&Kq)9(:;	
 	

s   =A   	A8	$A3-A83A8c                    t        | j                  | j                  j                         | j                  dd| j
                  ddd	      S )u   IdlePRSnapshot → PRMeta (merge_queue_executor 호환).

    누락 필드는 보수적 기본값 (BLOCKED / unresolved) 으로 채워 fail-closed.
    mainr   GEMINI_UNRESOLVEDBLOCKEDr   )	r   rk   head_refbase_refchanged_filesci_required_all_successgemini_statusmerge_state_statusqueue_predecessors_open)r   r   rk   lowerrC  rF  )r   s    r8   r   r     sK    
 ""((*"" ( @ @)$ !
 
r:   c                 h    t        j                  t        j                        j	                  d      S )Nseconds)timespec)r   r   r   utc	isoformatr   r:   r8   r   r     s#    <<%///CCr:   )rS   rU   rV   rX   rZ   r\   r^   r`   rb   r1   r3   rd   r/   r-   r.   r,   r   rg   r   r>  )r7   BaseExceptionrz   r   )r7   rP  rz   r  )rj   rD   ri   rh   r1  rD   r2  rD   r   r+  r3  r+  r4  r   r5  r   r6  rh   r.  ztuple[dict, ...]r/  z!Callable[[int, str], dict] | Noner   zPath | Nonerz   r  )r   r   rz   r   )rz   rD   )^r}   
__future__r   r   r   loggingr  
contextlibr   dataclassesr   r   r   pathlibr   typingr	   r
   r   r   anu_v2.idle_pr_diagnoserr   r   r   r   r   r   r   r   anu_v2.merge_queue_executorr   r   r   r   r   r   anu_v2.owner_trigger_auditr   r   r   anu_v2.owner_trigger_decisionr   anu_v2.owner_trigger_onlyr   r    r!   r"   anu_v2.polling_policyr#   r$   r%   r&   r'   anu_v2.second_review_recoveryr(   r)   r*   r+   	getLoggerrB   r  r,   r~   r-   r.   r/   r1   r3   PermissionErrorr  MemoryErrorr   r   AttributeErrorr4   r9   rQ   rS   rU   rV   rX   rZ   r\   r^   r`   rb   rd   rg   r   r   r>  r   r   __all__r   r:   r8   <module>rc     se  > #    	 % ! '  6 6	 	 	  
 ?    
		8	$&M  M'U * U%C 
 C* )T : S+B j B5V &
 V @ < 1
. /I  H+B j B*@ Z @"< Z <"< Z <$4 z 4*@ Z @': * :
 1M !: L /H  G $K K K@ $     4F
& F
&B 57:> $<
<
 <
 	<

 <
 (<
 '<
 !%<
  $<
 &)<
 "2<
 8<
 <
 
<
~$Dr:   