
    Mj=                       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 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 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-m.Z.m/Z/m0Z0m1Z1m2Z2 dZ3de4d<   dZ5de4d<   dZ6de4d<   dZ7de4d<   dZ8de4d<   dZ9de4d<   e*e:e;e<e=e>e?fZ@de4d<   d;dZAd<dZBd ZCde4d!<   d"ZDde4d#<   d$ZEde4d%<   d&ZFde4d'<   d(ZGde4d)<   d*ZHde4d+<   d,ZIde4d-<   d.ZJde4d/<    e	d01       G d2 d3             ZK e	d01       G d4 d5             ZL G d6 d7      ZMd=d8ZNd>d9ZOg d:ZPy)?u  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_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_nowz%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 박제) 대상인지 판정.)
isinstancer/   )excs    J/home/jay/workspace/.worktrees/task-2699-dev1/anu_v2/executor_scheduler.py_is_critical_exceptionr4   |   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)r7   __name__
__module__str__traceback__tb_nexttb_framef_coder   co_filenamenameco_name	tb_lineno)r2   summarytblastcodes        r3   _summarize_exceptionrL      s     S	""s)&&s8DS>G
 
		B	~ll&<<D ll&}}##%)$*:*:%;%@%@!"%)\\!"#'>> Nr5   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_DEDUPEDT)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_numberr?   task_idhead_shastateaction runner_resultdecision_pathmarker_pathreasonc                    | j                   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)rd   rN   rP   rQ   rS   rU   rW   rY   r[   r,   r.   
ValueErrorselfs    r3   __post_init__zSchedulerPRAction.__post_init__   sW    ;;+('!'$(2 
 
 wt{{o5HIJJ
r5   N)returnNone)
r=   r>   __qualname____doc____annotations__rf   rg   rh   ri   rp    r5   r3   r^   r^      sL    9NLMJKM3M3KFCKr5   r^   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).
    r?   cycle_started_atcycle_finished_atztuple[SchedulerPRAction, ...]
pr_actionsr   r_   chat_notificationsrechecks_doneTboolbot_should_exitpr_exceptions_isolated pr_exceptions_critical_escalatedFcycle_crashedN)r=   r>   rs   rt   ru   r|   r}   r   r   r   r   rv   r5   r3   rx   rx      sZ    	 --M3 OT "#C#,-$c-M4r5   rx   c                  L   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	 	 	 	 	 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 '/')NotImplementedErrorr1   r   	TypeErrorr   r?   rm   r   resolve_workspace_root_decision_dir_snapshot_provider_owner_trigger_merge_executor_owner_repor   
_diagnoser_now_iso_clock)
ro   workspace_rootdecision_dirsnapshot_providerowner_triggermerge_executorownerrepor   r   s
             r3   __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r5   c                (    | j                   t        z  S )u.   scheduler same-head dedupe lock 파일 경로.)r   r'   rn   s    r3   	lock_pathzExecutorScheduler.lock_path  s     ##&===r5   c                (    | j                   t        z  S )u5   scheduler audit JSONL 경로 (cycle / action 박제).)r   r(   rn   s    r3   
audit_pathzExecutorScheduler.audit_path#  s     ##&>>>r5   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)ro   lock_fhs     r3   _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)ro   r`   rb   audits       r3   _has_active_trigger_for_headz.ExecutorScheduler._has_active_trigger_for_head:  s+     ##**((IH(EEr5   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 반환.
        )r`   rb   z8audit shows active POSTED|PENDING trigger for (pr, head))r`   ra   rb   rc   rd   rf   ri   )ra   r   r   z.owner_trigger_decision.json)rg   r   r   current_head_actualdecision_invalid_coderK   re   )ra   r   outcome_coder   extrazdecision invalid: )	r`   ra   rb   rc   rd   rf   rg   rh   ri   N)ra   r   r   r   z,runner returned DEDUPED (atomic dedupe race))r`   ra   rb   rc   rd   rf   rg   ri   rf   zrunner result )r   r`   rb   r^   ra   rc   r[   r   _snapshot_to_pr_metar   emit_owner_trigger_decisionr   r!   r   r   r   r   record_owner_trigger_outcomer   getattrrQ   r   r?   r   r   rN   ri   rP   )ro   diagsnapshotpr_metarg   rf   r2   rh   s           r3   _dispatch_owner_triggerz)ExecutorScheduler._dispatch_owner_triggerF  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.)ra   r   latest_gemini_review_commit_idr   re   rh   )r`   ra   rb   rc   rd   rg   rh   ri   )r   r    auto_resume_after_fresh_evidencera   latest_gemini_commit_idr   decisionr   ri   r   r   r?   getr^   r`   rb   rc   rS   )ro   r   r   r   outcome
marker_strr   s          r3   _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]]**&">>	
 		
r5   )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   rl   zsnapshot missingr`   ra   rb   rc   rd   ri   )r   r   cycle_started   )r   rd   F)	ry   rz   r{   r|   r}   r   r   r   r   )r    r#   r&   r"   r}   elapsed_secondsr   r   listr   r   diagnose_allnumberr   r`   appendr^   ra   rb   rc   _safe_handle_single_diagnosisrd   r,   r.   _append_auditrx   tuple)ro   r   r   r   rc   r   actionsisolated_countcritical_count	snapshots	diagnosess
snap_by_prr   snaprd   cycle_finisheds                    r3   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               6   |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        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   rl   )rc   r   r^   r`   ra   rb   rU   ri   rY   rW   r   r   r   r   r   )ro   r   r   s      r3   _handle_single_diagnosisz*ExecutorScheduler._handle_single_diagnosisE  sN    ::22$..jj*{{  ::**$..jj2{{  ::$$..jj,{{  ::33..D8.LL::.&
 
 //TH/MM nnLL]]**';;
 	
r5   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   rH   criticalr   re   zcritical exception r7   z: r9   u*    — cycle 유지, ESCALATED marker 박제zisolated exception u'    — cycle 유지, FAILED marker 박제)r`   ra   rb   rc   rd   rf   rh   ri   N)r   r"   	Exceptionr4   rL   _record_pr_exception_markerr.   r,   r^   r`   ra   rb   rc   )
ro   r   r   r   r2   r   rH   rh   action_coderi   s
             r3   r   z/ExecutorScheduler._safe_handle_single_diagnosisy  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failedzpr-z.pr-z.exception.json.r   )schematsry   r`   ra   rb   rc   r   rd   exception_summaryr   r   r|   wr   r   F   )ensure_ascii	sort_keysindentN)r   )r   r   ra   r`   r*   r   rb   rc   r.   r,   r   r   r   r   r   r   r   writejsondumpsflushosfsyncr   touchr?   )ro   r   rH   r   r   r   suffix	task_partsummary_pathrh   recordfhs               r3   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   ry   r`   ra   rb   rc   rd   rf   rg   rh   ri   r   r   r|   r   r   r   F)r   r   
N)r   r   r   r)   r   r`   ra   rb   rc   rd   rf   rg   rh   ri   r   r   r   r   r   r   r   r   r   r  r  r  r  r   )ro   r   rd   r	  r
  s        r3   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$   )ro   r   r   rc   s       r3   schedule_recheckz"ExecutorScheduler.schedule_recheck  s/      	*/:':'F#L+M
 u%%r5   )r   
str | Pathr   r  r   z&Callable[[], Sequence[IdlePRSnapshot]]r   r   r   r   r   r?   r   r?   r   zIdlePRDiagnoser | Noner   zCallable[[], str] | Nonerq   rr   )rq   r   )rq   zIterator[None])r`   r_   rb   r?   rq   r~   )r   r   r   r   rq   r^   )r   zdict | Noner   z
str | Noner   PollingState | Nonerq   rx   )r   r   r   r   r   r?   rq   r^   )
r   r   rH   dictr   r~   r   r?   rq   r?   )r   r?   rd   r^   rq   rr   )r   r_   r   r  rq   r#   )r=   r>   rs   rt   r   propertyr   r   r   r   r   r   r   r   r   r   r   r   r  rv   r5   r3   r   r      s8   H -1*.? #? !	?
 B? (? +? ? ? *? (? 
?F > > ? ? = =FF+.F	Fx
 x
 !	x

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

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

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

 
0
h> > !	>
 > 
>@1  1  	1 
 1  1  
1 j 8  8 "	 8
 
 8P 48	& & 1	&
 
&r5   r   c                    t        | j                  | j                  j                         | j                  dd| j
                  ddd	      S )u   IdlePRSnapshot → PRMeta (merge_queue_executor 호환).

    누락 필드는 보수적 기본값 (BLOCKED / unresolved) 으로 채워 fail-closed.
    mainrv   GEMINI_UNRESOLVEDBLOCKEDr   )	r   rb   head_refbase_refchanged_filesci_required_all_successgemini_statusmerge_state_statusqueue_predecessors_open)r   r   rb   lowerr  r  )r   s    r3   r   r   -  sK    
 ""((*"" ( @ @)$ !
 
r5   c                 h    t        j                  t        j                        j	                  d      S )Nseconds)timespec)r   r   r   utc	isoformatrv   r5   r3   r   r   ?  s#    <<%///CCr5   )rN   rP   rQ   rS   rU   rW   rY   r[   r,   r.   r*   r(   r)   r'   rx   r^   r   )r2   BaseExceptionrq   r~   )r2   r$  rq   r  )r   r   rq   r   )rq   r?   )Qrt   
__future__r   r   r   r  
contextlibr   dataclassesr   r   r   pathlibr   typingr	   r
   r   r   anu_v2.idle_pr_diagnoserr   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&   r'   ru   r(   r)   r*   r,   r.   PermissionErrorOSErrorMemoryErrorr   r   AttributeErrorr/   r4   rL   rN   rP   rQ   rS   rU   rW   rY   r[   r^   rx   r   r   r   __all__rv   r5   r3   <module>r5     s  > #   	 % ! '  6 6    
 ?   'N  M'U * U%C 
 C* )T : S+B j B5V &
 V @ < 1
. /I  H+B j B*@ Z @"< Z <"< Z <$4 z 4*@ Z @': * : $K K K> $     4G	& G	&Z$Dr5   