
    QjL                         d Z ddlmZ ddlmZmZmZ ddlmZmZm	Z	m
Z
mZ dZdZdZ eeeh      ZdZd	Zd
ZdZdZdZdZdZdZdZ e ed            ZddZddZded	 	 	 	 	 	 	 	 	 ddZdd	 	 	 	 	 	 	 ddZy)uF  utils.chair_authorization_validator — anu.chair_merge_authorization.v1 validator.

task-2637 — real merge executor wiring 코드화 (activation false default · 실제 merge 실행 0).

Spec: memory/specs/system_real_merge_executor_wiring_spec_260523.md §6
sha256: bcaf654e981a43083af50879164021c918eeac9753cad3b3ad146209a1a62765

회장 verbatim (spec §6.1 / §6.2 + 회장 10결정 #2):
    schema = "anu.chair_merge_authorization.v1"
    scope ∈ {"per_pr", "batch"}
    pr_numbers : list of explicit PRs (per_pr=1건 / batch=N건)
    head_shas  : 본 SHA 외 머지 시도 금지 (head 변경 시 재승인 강제)
    expires_at_kst : TTL 미래 cap (≤24h 권장)
    chair_signature : 회장 verbatim 메시지 (verbatim token signature — 회장 결정 #2)
    issued_at_kst / task_id / expected_files_snapshot

검증:
    * expires_at_kst 경과 → NO_OP_NO_AUTHORIZATION
    * pr_identity.pr ∉ pr_numbers → NO_OP_NO_AUTHORIZATION
    * pr_identity.head_sha ∉ head_shas → NO_OP_NO_AUTHORIZATION
    * chair_signature 미수록/비-string → NO_OP_NO_AUTHORIZATION
    )annotations)datetime	timedeltatimezone)AnyDictListOptionalTuplez anu.chair_merge_authorization.v1per_prbatch   )schemascope
pr_numbers	head_shasexpires_at_kstchair_signatureissued_at_ksttask_idAUTH_OKAUTH_MISSINGAUTH_INVALID_SCHEMAAUTH_EXPIREDAUTH_PR_MISMATCHAUTH_HEAD_SHA_MISMATCHAUTH_SIGNATURE_MISSINGAUTH_TTL_TOO_LARGE	   )hoursc                    t        | t              r| syd}|D ]>  }	 t        j                  | |      }|j                  |j                  t              }|c S  y# t        $ r Y Mw xY w)u   Best-effort parse of a KST-formatted timestamp.

    Accepts:
      * ``YYYY-MM-DD HH:MM`` and ``YYYY-MM-DD HH:MM:SS`` (no tz suffix → KST assumed)
      * ``YYYY-MM-DDTHH:MM:SS+09:00`` (explicit KST tz)
    N)z%Y-%m-%dT%H:%M:%S%zz%Y-%m-%d %H:%M:%S%zz%Y-%m-%dT%H:%M:%Sz%Y-%m-%d %H:%M:%Sz%Y-%m-%d %H:%Mtzinfo)
isinstancestrr   strptimer#   replace_KST
ValueError)text
candidatesfmtdts       T/home/jay/workspace/.worktrees/task-2643-dev6/utils/chair_authorization_validator.py
_parse_kstr/   ?   sz     dC J  	""4-Byy ZZtZ,I   		s   9A	A&%A&c                4   | 6t        j                  t        j                        j	                  t
              S  |        }t        |t               r$|j                  r|S |j                  t
              S t        t        |            }|t        d|      |S )Nr"   z#clock() returned unparsable value: )r   nowr   utc
astimezoner(   r$   r#   r'   r/   r%   r)   )clockvalparseds      r.   _now_kstr7   Z   s}    }||HLL)44T::
'C#x jjs>ckkk&>>C!F~>sgFGGM    N)r4   max_ttl_hoursc               r   g }| 
dt         dgfS t        | t              s dt         dt        |       j                   gfS | j                  d      t        k7  r*|j                  dt        d| j                  d             t        D ]  }|| vs|j                  d|         | j                  d	      }|t        vr$|j                  d
t        t               d|       |r	dt        |fS | j                  d      }t        |t              r|j                         s
dt        dgfS | j                  d      }| j                  d      }	t        |t              rt        |      nd}
t        |	t              rt        |	      nd}|
dt        d|gfS |dt        d|	gfS t!        |      }|
|k  r,dt"        d|
j%                          d|j%                          gfS |
|z
  j'                         }||dz  kD  r7dt(        d|dz  dd| d|j%                          d|
j%                          d	gfS | j                  d      }| j                  d      }t        |t*              r|s
dt        dgfS t        |t*              r|s
dt        dgfS |t,        k(  r%t/        |      dk7  rdt        dt/        |       dgfS t        |t              r|j                  d       nd}t        |t              r|j                  d!      nd}|d}n	 t1        |      }|D cg c]?  }t        |t0              s"t        |t              s$|j7                         s5t1        |      A }}|||vrdt8        d"|d#|gfS t        |t              r||vrdt:        d$|d%|d&gfS d't<        g fS # t2        t4        f$ r d}Y w xY wc c}w )(uE  Validate a chair_merge_authorization JSON payload against pr_identity.

    Returns:
        (ok, code, reasons) — ``code`` is one of the AUTH_* enums.

    Verbatim-token signature doctrine (spec §6.2 / 회장 결정 #2):
        chair_signature MUST be present, non-empty, and a string. The actual
        signature *content* is compared verbatim by the caller against the
        expected chair-issued token; this validator only enforces presence and
        type. HMAC-based hardening is reserved for a follow-up task (회장 결정
        #2 후속 hardening 후보).
    NFzchair_authorization is Nonez&chair_authorization must be dict, got r   zschema must be z, got zrequired key missing: r   zscope must be one of r   z#chair_signature is missing or emptyr   r   zexpires_at_kst unparsable: zissued_at_kst unparsable: zexpires_at_kst u	    ≤ now i  zTTL z.2fzh > cap zh (issued_at=z, expires_at=)r   r   z#pr_numbers must be a non-empty listz"head_shas must be a non-empty list   z6per_pr scope requires exactly 1 pr in pr_numbers (got prhead_shazpr_identity.pr z not in authorized pr_numbers=zpr_identity.head_sha z not in authorized head_shas=u7    (head change requires re-authorization — spec §6.2)T)r   r$   dicttype__name__getCHAIR_AUTH_SCHEMAappendREQUIRED_AUTH_KEYSCHAIR_AUTH_SCOPESsortedr   r%   stripr   r/   r7   r   	isoformattotal_secondsr   listCHAIR_AUTH_SCOPE_PER_PRlenint	TypeErrorr)   isdigitr   r   r   )authorizationpr_identityr4   r9   reasonskeyr   sig
expires_at	issued_at
expires_dt	issued_dtr1   ttl_secondsr   r   r=   r>   pr_intp
normalizeds                        r.   validate_chair_authorizationr^   f   sU   & Gl%B$CCCmT*l'MdS`NaNjNjMk%l$mmm "&77/2&9J9J89T8WX	

 " ;m#NN3C59:;
 g&E%%.v6G/H.IPUyYZ)722 

-
.Cc3syy{,/T.UUU ""#34J!!/2I+5j#+FJ'DJ)3Is)C
9%I).I*,X+YYY).H,V+WWW
5/CSlj2245Ys}}>OP%
 
 	

 	)88:K]T))(;%c*(=/ B#--/0j>R>R>T=UUVX+
 
 	
 ""<0J!!+.Ij$'z),Q+RRRi&i),P+QQQ''C
Oq,@)DS_DUUVW,
 
 	
 #-[$"?	TB.8d.K{z*QUH	z $	WF #jC&8Z3=OTUT]T]T_AJ  ~z1&bV#A*P)
 
 	
 h$	(A,#H</LYM ZE E/
 
 	

 " :& 	F	s$   N %N49N4
N4N10N1r4   c               *    t        | ||      \  }}}|S )zHConvenience predicate. True iff validate_chair_authorization returns ok.r_   )r^   )rQ   rR   r4   ok_code_reasonss         r.   is_authorizedrd      s#     7{%Bx Ir8   )r*   r%   returnzOptional[datetime])r4   Optional[Any]re   r   )
rQ   r   rR   Dict[str, Any]r4   rf   r9   rN   re   zTuple[bool, str, List[str]])rQ   r   rR   rg   r4   rf   re   bool) __doc__
__future__r   r   r   r   typingr   r   r	   r
   r   rC   rL   CHAIR_AUTH_SCOPE_BATCH	frozensetrF   CHAIR_AUTH_MAX_TTL_HOURSrE   r   r   r   r   r   r   r   r   r(   r/   r7   r^   rd    r8   r.   <module>rp      s  , # 2 2 3 36 "   68NOP   	  + % 1 1 ) 	"#6	   1kkk 	k
 k !kd  	


 	

 

r8   