
    Mj1T                    "   U d Z ddlm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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 d	Zd
ed<   dZd
ed<    ej:                  d      Zded<    ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d       ej:                  d      f
Zded<   dZ ded<    ed !       G d" d#             Z! G d$ d%e"      Z# G d& d'e"      Z$ G d( d)e%      Z& G d* d+e"      Z'd3d,Z(d4d-Z)d5d6d.Z* G d/ d0      Z+	 	 	 	 	 	 	 	 	 	 	 	 d7d1Z,d6d2Z-y)8u9  anu_v2.owner_trigger_only — OWNER_TRIGGER_ONLY_CAPABILITY core module.

회장 §명시 14장 1:1 박제 (2026-05-11 KST, OWNER_TRIGGER_ONLY_CAPABILITY).
task-2554+1 회장 §명시 (2026-05-12 KST) HIGH race condition fix 반영.
task-2554+2 회장 §명시 (2026-05-12 KST) §1 1:1 완성:
  - ``RESULT_PENDING`` import + sentinel 활용.
  - ``http_post`` 호출 직전 ``txn.record(PENDING)`` 으로 영구 기록 — process crash 후
    다음 runner 가 DEDUPED 판정 (crash-safety fail-closed).

본 모듈 범위:
  - public action **1 종**: ``POST_GEMINI_REVIEW_TRIGGER_COMMENT``
  - comment body 상수 (외부 입력 X): ``COMMENT_BODY = "/gemini review"``
  - 단일 허용 endpoint: ``POST /repos/{owner}/{repo}/issues/{pr_number}/comments``
  - 금지 11 endpoint hard-block (``PermissionError`` raise)
  - 전용 token 1 종: ``OWNER_GEMINI_TRIGGER_TOKEN`` (다른 token fallback 0)
  - merge path 와 완전 분리: ``BOT_GITHUB_TOKEN`` 본 모듈 어디서도 사용 0
  - dedupe atomic (audit JSONL + fcntl flock + sidecar lock transaction)
  - token redaction guard (audit 에 raw value 미출력)
  - decision JSON v1 검증 fail-closed
  - result enum: POSTED / DEDUPED / FAILED / PENDING

result 흐름 (task-2554+2 §1):
  1. txn.check_dedupe (POSTED + PENDING)
  2. (DEDUPED 인 경우 audit DEDUPED + return)
  3. token presence + hash_prefix 계산
  4. **txn.record(PENDING)** — http_post 직전 영구 기록 (crash-safety)
  5. http_post 호출
  6. 성공: txn.record(POSTED) / 실패: txn.record(FAILED)

one-way isolation: anu_v2/ 외부 import 금지.
    )annotationsN)	dataclass)Path)CallableFinal)AuditRedactionErrorDedupeViolationOwnerTriggerAuditRESULT_DEDUPEDRESULT_FAILEDRESULT_PENDINGRESULT_POSTEDtoken_hash_prefix)ALLOWED_ACTIONALLOWED_COMMENT_BODYDecisionInvalidErrorvalidate_decisionz/gemini reviewz
Final[str]COMMENT_BODYOWNER_GEMINI_TRIGGER_TOKENTOKEN_ENV_NAMEz(^/repos/[^/]+/[^/]+/issues/\d+/comments$zFinal[re.Pattern[str]]_ALLOWED_PATH_REz/pulls/\d+/merge\bz/pulls/\d+/reviews\bz/git/refs\bz/contents\bz/issues/\d+(?:\?|$)z	/labels\bz/branches\bz$/actions/(?:runs|workflows)/.*/rerunz3/check-runs/.*/rerequest|/check-suites/.*/rerequestz/pulls/\d+\?|/pulls/\d+$z"Final[tuple[re.Pattern[str], ...]]FORBIDDEN_ENDPOINT_PATTERNS)BOT_GITHUB_TOKENGH_TOKENGITHUB_TOKEN	OWNER_PAT	PAT_TOKENzFinal[tuple[str, ...]]FORBIDDEN_TOKEN_ENV_NAMEST)frozenc                  p    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Zded<   y)TriggerResultuP   ``trigger_gemini_review`` 결과 객체. token / authorization header 미포함.strstatusintprheadactioncomment_bodyendpointbooltoken_presentr   N
str | None
error_code)__name__
__module____qualname____doc____annotations__r-        J/home/jay/workspace/.worktrees/task-2699-dev1/anu_v2/owner_trigger_only.pyr!   r!   _   s:    ZKG
IKM!J
!r4   r!   c                      e Zd ZdZy)ForbiddenEndpointErroru9   금지 11 endpoint 호출 시도 (회장 §7 hard-block).Nr.   r/   r0   r1   r3   r4   r5   r7   r7   q   s    Cr4   r7   c                      e Zd ZdZy)TokenBoundaryViolationu-   다른 token 사용 시도 (회장 §6 fail).Nr8   r3   r4   r5   r:   r:   u   s    7r4   r:   c                      e Zd ZdZy)CommentBodyViolationuK   COMMENT_BODY 외부 변조 / 외부 입력 사용 시도 (회장 §4 fail).Nr8   r3   r4   r5   r<   r<   y   s    Ur4   r<   c                      e Zd ZdZy)MergePathViolationuG   merge_queue_executor merge path 와의 분리 위반 (회장 §9 fail).Nr8   r3   r4   r5   r>   r>   }   s    Qr4   r>   c                    t        | t              r| rd| v rt        d      t        |t              r|rd|v rt        d      t        |t              rt        |t              s|dk  rt        d      d|  d| d| dS )	N/z,owner must be a non-empty string without '/'z+repo must be a non-empty string without '/'r   z pr_number must be a positive intz/repos/z/issues/z	/comments)
isinstancer"   
ValueErrorr$   r*   )ownerrepo	pr_numbers      r5   _build_post_comment_pathrF      s    eS!#,GHHdC tFGGi%It)D	UV;<<UG1TF(9+Y??r4   c                6   t        | t              r| j                         dk7  rt        d|       t        |t              st        d      t        D ]!  }|j                  |      st        d|       t        j                  |      st        d|      y)z+static + dynamic endpoint hard-block guard.POSTzforbidden method: zpath must be stringzforbidden endpoint path: z8path not in allow-list (POST issues/{n}/comments only): N)rA   r"   upperr7   r   searchr   match)methodpathpats      r5   assert_endpoint_allowedrO      s    fc"flln&>$'9&%DEEdC $%:;;* O::d(+DTH)MNNO !!$'$HQ
 	
 (r4   c                T    | yt         D ]  }|| v st        d| dt         d       y)u   token 경계 검증 (회장 §6).

    명시적으로 ``env`` dict 에 다른 token env 이름이 키로 전달되면 fail.
    Nzforbidden token env injected: u!    — owner trigger path must use  only)r   r:   r   )env	forbiddens     r5   assert_token_boundaryrT      sI    
 {. 	(0 <*+52 r4   c                  z    e Zd ZdZdd	 	 	 	 	 	 	 	 	 ddZddZddd	 	 	 	 	 	 	 	 	 	 	 	 	 ddZd Zd	 Zd
 Z	d Z
y)OwnerTriggerOnlyu3  OWNER_TRIGGER_ONLY_CAPABILITY 단일 진입점.

    public action: ``trigger_gemini_review(decision_path, owner, repo, current_head_actual)``.

    side-effect 추상화 (test injection):
      - ``http_post``: ``Callable[[method:str, path:str, body:dict, headers:dict], dict]``
      - ``token_provider``: ``Callable[[], str]`` — OWNER_GEMINI_TRIGGER_TOKEN value 반환.

    task-2554+2 §1 흐름:
      1. token boundary 검증
      2. decision JSON 검증
      3. comment_body 검증
      4. endpoint hard-block
      5. ── ATOMIC TRANSACTION ────────────────────────────────
         5a. audit.transaction() 진입 (sidecar lock LOCK_EX)
         5b. txn.check_dedupe (POSTED + PENDING)
         5c. DEDUPED 시 audit record + return
         5d. token presence + hash_prefix 계산
         5e. **txn.record(PENDING)** — http_post 호출 직전 영구 기록 (crash-safety)
         5f. http_post 호출
         5g. 성공 시 audit POSTED / 실패 시 audit FAILED
      6. lock 해제
    N)auditc                   |t        d      |t        d      t        |      j                         | _        || _        || _        ||| _        y t        | j                        | _        y )Nz#http_post callable must be injectedz(token_provider callable must be injected)NotImplementedErrorr   resolve_workspace_root
_http_post_token_providerr
   _audit)selfworkspace_root	http_posttoken_providerrW   s        r5   __init__zOwnerTriggerOnly.__init__   sk     %&KLL!%&PQQ#N3;;=#-$0e6GH\H\6]r4   c                    t        |      }|j                         s| j                  |z  }|j                         st	        dd|       t        j                  |j                  d            }|S )NE_FILE_MISSINGzdecision file not found: zutf-8)encoding)r   is_absoluter[   existsr   jsonloads	read_text)r_   decision_pathrM   datas       r5   _read_decisionzOwnerTriggerOnly._read_decision   sg    M"!''$.D{{}&'7;TUYTZ9[\\zz$..'.:;r4   )env_overrider(   c                  t        |       | j                  |      }t        ||       |t        n|}|t        k7  rt        dt        d|      t        |d         }	|d   }
t        |||	      }t        d|       t        |      }|j                  dd	      }| j                  j                         5 }	 |j                  |	|

       | j'                         }t)        |t              r|st+        t,         d      t/        |      }|j                  ||	|
t         t0        t        ||d|dd       d| ddd}dt        i}	 | j3                  d|||       	 i }d	}	 |j                  ||	|
t         t8        t        ||d|dd       t%        t8        |	|
t         t        |d|d	      cddd       S # t        $ rV |j                  ||	|
t         t"        t        ||dd	ddd       t%        t"        |	|
t         t        |dd	d	      cY cddd       S w xY w# t4        $ r?}	 |j                  ||	|
t         t6        t        ||d|ddd       i }d	}|# i }d	}w xY wd}~ww xY w# i }d	}w xY w# t        t:        f$ r  w xY w# 1 sw Y   yxY w)u  단일 public action. /gemini review 댓글 1회 작성.

        race condition: 동시 2 process 가 같은 (pr, head) 호출 시 lock 직렬화로 후속
        process 의 check_dedupe 가 항상 DedupeViolation 차단.

        crash-safety (task-2554+2 §1): http_post 호출 직전 ``txn.record(PENDING)`` 으로
        영구 기록. process crash 후 다음 runner 가 ``_has_active_trigger`` 에서 PENDING
        발견 → DedupeViolation → DEDUPED 판정 (fail-closed).
        )current_head_actualNzcomment_body must be z, got r%   current_headrH   task_id )r%   r&   FDEDUPE)rs   r%   r&   r'   resultr(   r)   rl   r+   r   token_value_loggedr-   )	r#   r%   r&   r'   r(   r)   r+   r   r-   u+    not provided — owner trigger fail-closedT)rs   r%   r&   r'   rv   r(   r)   rl   r+   r   rw   zBearer zapplication/vnd.github+jsonz
2022-11-28)AuthorizationAcceptzX-GitHub-Api-VersionbodyHTTP_POST_FAIL)rT   rn   r   r   r   r<   r$   rF   rO   r"   getr^   transactioncheck_deduper	   recordr   r   r!   r]   rA   r:   r   r   r   r\   	Exceptionr   r   r   )r_   rl   rC   rD   rq   ro   r(   decisionrz   pr_numr&   rM   decision_path_strtask_id_valtxntokenhash_prefixheadersbody_payloadexcs                       r5   trigger_gemini_reviewz&OwnerTriggerOnly.trigger_gemini_review   s   ( 	l+ &&}5(8KL  ,3|''&'(<'?vdXN 
 Xd^$''tV<-.ll9b1 [[$$& @	#  F 6> ((*EeS),%&&QR  ,E2K
 JJ*  ,,$0 $%6%))4*/$ $+5'!27(4G
 #L1LlGD4 

#.$ $"0"/(4$():)--8.3$ !$%)""-
m@	 @	 # 

#.$ $"0"0(4$():).-/.3&.  %))!-!"'&('
 
+@	 @	D  JJ'2"($(&4&3,8(,->-11<27*:" !GE	 !GE-2 & $%89 g@	 @	s   5I:7F0
A9I:HI:)I&I:0AHI:HI:	I*IIIIIII##I:&I77I::Jc                    t        d      )NzZowner_trigger_only must not perform merge. Use merge_queue_executor with BOT_GITHUB_TOKEN.)r>   r_   argskwargss      r5   mergezOwnerTriggerOnly.merge  s     >
 	
r4   c                    t        d      )Nz)approve forbidden (review submit blocked)r7   r   s      r5   approvezOwnerTriggerOnly.approve  s    $%PQQr4   c                    t        d      )Nzclose forbiddenr   r   s      r5   closezOwnerTriggerOnly.close  s    $%677r4   c                    t        d      )Nzreopen forbiddenr   r   s      r5   reopenzOwnerTriggerOnly.reopen  s    $%788r4   )
r`   
str | Pathra   z&Callable[[str, str, dict, dict], dict]rb   zCallable[[], str]rW   zOwnerTriggerAudit | NonereturnNone)rl   r   r   dict)rl   r   rC   r"   rD   r"   rq   r"   ro   dict | Noner(   r,   r   r!   )r.   r/   r0   r1   rc   rn   r   r   r   r   r   r3   r4   r5   rV   rV      s    < +/^ #^ :	^
 *^ (^ 
^&$ %)#'k "k 	k
 k !k "k !k 
k^
R89r4   rV   c               Z   ddl m} ddlm} t	        | t
              st        d      	 | j                  ||||      }|j                  t         t        t        t"        fvrt        S |j                  S # t        t        t        t        |f$ r  |$ r	 t        cY S t        $ r	 t        cY S w xY w)u  ExecutorScheduler → OwnerTriggerOnly 호출 어댑터 (task-2556 §6).

    회장 §6 명시 1:1: ``owner_trigger_only.trigger_gemini_review()`` 를 scheduler 가
    자동 호출. 본 wrapper 는 runner 결과를 ``merge_queue_executor.orchestrate_owner_trigger_
    for_stale_pr`` 가 기대하는 string 형식 ("POSTED" / "DEDUPED" / "FAILED") 으로 정규화.

    설계 원칙:
      - 본 함수는 token 을 직접 만지지 않는다. ``runner`` 에 이미 ``token_provider`` 가
        주입되어 있어야 한다 (token boundary §11).
      - http_post 예외는 본 함수에서 catch — TokenBoundaryViolation / ForbiddenEndpointError /
        CommentBodyViolation 는 그대로 raise (fail-closed).
      - 일반 RuntimeError 는 "FAILED" 로 정규화 (transient 가능성, FAILED marker 가 caller
        에서 생성됨).

    Args:
      runner: OwnerTriggerOnly 인스턴스 (token_provider/http_post 이미 주입).
      decision_path: owner_trigger_decision.json 경로 (Path 또는 str).
      owner: GitHub owner.
      repo: GitHub repo.
      current_head_actual: 실측 PR head SHA (40-char hex).

    Returns:
      "POSTED" | "DEDUPED" | "FAILED" | "PENDING" — orchestrate_owner_trigger_for_stale_pr
      가 사용하는 enum.

    Raises:
      TokenBoundaryViolation: token 부재.
      ForbiddenEndpointError: 금지 endpoint 호출 시도.
      CommentBodyViolation: comment body 변조.
      MergePathViolation: merge path 침범.
      DecisionInvalidError: decision schema 위반.
    r   )r   )r	   z(runner must be OwnerTriggerOnly instance)rl   rC   rD   rq   )anu_v2.owner_trigger_decisionr   anu_v2.owner_trigger_auditr	   rA   rV   	TypeErrorr   r:   r7   r<   r>   r   r   r   r#   r   r   )runnerrl   rC   rD   rq   _DecisionInvalidError_DedupeViolationrv   s           r5   invoke_from_schedulerr     s    P \Nf./BCC--' 3	 . 
" }}]NM>ZZ== #$: "46KM  	  s   A2 2%B*B*)B*c                   | t        d      t        D ]  }|| v st        d|dt         d       t        | vrt        dt         d      | j                  t              }t	        |t
              r|st        t         d      y)	uF  scheduler 진입 시 token boundary 검증 (task-2556 §11).

    scheduler 가 BOT_GITHUB_TOKEN / GH_TOKEN / OWNER_PAT 등을 들고
    owner_trigger 경로로 진입하려는 시도를 hard-block. 회장 §11 / §16 1:1.

    또한 ``OWNER_GEMINI_TRIGGER_TOKEN`` 이 env 에 명시되어 있지 않으면 fail-closed.
    Nz>scheduler env must be provided for token boundary verificationz,scheduler env contains forbidden token name u!    — owner_trigger path must use rQ   zscheduler env missing u    — owner_trigger fail-closedu-    present but empty/non-string — fail-closed)r:   r   r   r|   rA   r"   )rR   rS   token_values      r5   assert_scheduler_token_boundaryr     s     {$L
 	
 / 	(>ym L//=.>eE  S $$^$44RS
 	
 ''.)Kk3'{$KL
 	
 0;r4   )rC   r"   rD   r"   rE   r$   r   r"   )rL   r"   rM   r"   r   r   )N)rR   r   r   r   )r   z'OwnerTriggerOnly'rl   r   rC   r"   rD   r"   rq   r"   r   r"   ).r1   
__future__r   ri   redataclassesr   pathlibr   typingr   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r   r   r   r2   r   compiler   r   r   r!   PermissionErrorr7   r:   rB   r<   r>   rF   rO   rT   rV   r   r   r3   r4   r5   <module>r      s  @ #  	 !  "	 	 	  ,j +9
 9+52::/, (  BJJ$%BJJ&'BJJ~BJJ~BJJ%&BJJ|BJJ~BJJ67BJJEFBJJ*+C ? 5 1  $" " ""D_ D8_ 8V: VR R@
$s9 s9rCC C 	C
 C C 	CR
r4   