
     jp                    .   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mZ  ee      j#                         j$                  j$                  j$                  Z ee      ej*                  v r!ej*                  j-                   ee             ej*                  j/                  d ee             ddlmZmZmZmZ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( dd	l)m*Z*m+Z+m,Z, dDdEd
Z-dFdZ.dddddddddeddZ/de0d<   ddddddddd Z1de0d!<   d"ddddd#d$dd Z2de0d%<   d&ddddd'd(dd Z3de0d)<   d*ddddd+d,dd Z4de0d-<   d.ddddd/d0dd Z5de0d1<   d2Z6d3Z7d4Z8d5 Z9d6 Z:d7 Z;d8 Z<d9 Z=d: Z>d; Z?d< Z@d= ZAd> ZBd? ZCd@ ZDdA ZEdB ZFdC ZGy)Gu  tests/regression/test_bot_merge_identity_regression_2523.py

회귀 테스트 — task-2523 bot merge identity regression / autonomy proof harness.

회장 §본질:
  task-2522 BOT_MERGE_IDENTITY_SUCCESS path를 **회귀로 고정**한다.
  PR #73 (mergeCommit b7a37521..., mergedBy=app/jeon-jonghyuk-taskctl-bot,
  token_source=GITHUB_APP_INSTALLATION_TOKEN) 가 1회성 우연이 아니라 향후 PR에서도
  반복 가능한 표준 path 임을 박제한다.

회장 §명시 8 검증 (정확히 8개):
  1. GH_TOKEN process-local injection이 owner PAT보다 우선
  2. gh auth=owner여도 merge token_source=GITHUB_APP_INSTALLATION_TOKEN 기록
  3. mergedBy=app/jeon-jonghyuk-taskctl-bot → BOT_MERGE_IDENTITY_SUCCESS 분류
  4. mergedBy=JonghyukJeon → owner_pat fallback 분류
  5. token raw value 로그 0건
  6. branch cleanup도 bot token으로 가능
  7. smoke + reconcile 정상
  8. audit JSONL에 token_source / mergedBy / owner_pat_used / expected_bot_identity 4 필드 존재

replay fixture:
  - PR #73 BOT_MERGE_IDENTITY_SUCCESS (mergedBy=app/jeon-jonghyuk-taskctl-bot,
    mergeCommit=b7a37521c8f189bfd98b8ea047ebaa5c5fe684aa, mergedAt=2026-05-09T09:01:27Z)
  - PR #68/#69/#70/#71/#72 owner_pat fallback 5건 (autonomy capability gap 박제)

회장 §보안:
  - raw token value 절대 출력/저장 X (정적 grep 검증 포함)
  - gh api 실호출 X — 모두 runner mock / dataclass 직접 주입
  - subprocess 실행 X — 모든 경로는 fixture replay
    )annotationsN)fields)Path)AnyDictList)DEFAULT_AUDIT_JSONL_PATHMergeIdentityAuditRecordREQUIRED_AUDIT_FIELDS_2523TOKEN_SOURCE_GITHUB_APPTOKEN_SOURCE_OWNER_PATTOKEN_SOURCE_UNKNOWNappend_audit_jsonlbuild_audit_recordclassify_token_sourcecompute_autonomy_score_deltaexpected_bot_identity_for_actor'verify_branch_cleanup_token_inheritance)OWNER_PAT_FALLBACK_BLOCKEDexecute_squash_mergeselect_merge_token_decision)BlockedReasonclassify_capability_gapprobe_bot_merge_identityc                4    t        j                  g | ||      S )N)args
returncodestdoutstderr)
subprocessCompletedProcess)r   r   r   s      O/home/jay/workspace/tests/regression/test_bot_merge_identity_regression_2523.pycpr#   O   s    &&B:f]cdd    c                     dd fd}|S )u@   gh pr view <N> --json ... 호출에 응답하는 runner factory.c                (   ~~t        |       dk  s
| dd g dk7  rt        ddd      S 	 t        | d         }j	                  |      }|t        ddd	      S t        dt        j                  |      d      S # t        $ r t        ddd      cY S w xY w)
N   r      )ghprview    zunexpected callzbad pr numberz	Not Found)lenr#   int
ValueErrorgetjsondumps)r   cwdkwargsnpayloadpr_payloadss        r"   runnerz#make_pr_view_runner.<locals>.runnerV   s    t9q=D1I)==a.//	.DGA //!$?a[))!TZZ("--  	.a_--	.s   A8 8BB)N)r   	List[str]r4   r   returnsubprocess.CompletedProcess )r8   r9   s   ` r"   make_pr_view_runnerr>   S   s    . Mr$   I   app/jeon-jonghyuk-taskctl-botBotT)logintypeis_botztask/task-2522-dev2	task-25222026-05-09T09:01:27Z(b7a37521c8f189bfd98b8ea047ebaa5c5fe684aa)task_idmergedAtmergeCommitexpected_token_source)numbermergedByheadRefName_metazDict[str, Any]PR_73_FIXTURED   JonghyukJeonUserFz	task-2517z2026-05-09T00:53:24Z)rH   rI   )rL   rM   rO   PR_68_FIXTUREE   z	task-2519z2026-05-09T02:04:50ZPR_69_FIXTUREF   z	task-2518z2026-05-09T04:50:46ZPR_70_FIXTUREG   z	task-2520z2026-05-09T06:31:34ZPR_71_FIXTUREH   	task-2521z2026-05-09T13:30:00ZPR_72_FIXTUREghs_botFIXTUREsuffixghp_ownerFIXTUREsuffix)botFIXTUREsuffixownerFIXTUREsuffixc                 V   t         t        d} t        t        |       }|j                  t        k(  sJ d|j                          |j
                  du sJ |j                  t        dd k(  sJ t        j                  |j                               }t        D ]  }||vrJ d|        y)u7  (1) env에 owner PAT + GH_TOKEN=$BOT_GITHUB_TOKEN 동시 존재 →
    classify가 GH_TOKEN(ghs_) 값을 우선 분류 = GITHUB_APP_INSTALLATION_TOKEN.

    회장 §본질: process-local GH_TOKEN injection 패턴 (task-2522에서 PR #73 머지 시
    실제로 사용된 패턴) 이 owner PAT 보다 우선 적용되는지 회귀 박제.
    classify_token_source(token_value=...) 에 inject 된 token 값을 직접 전달하면
    그 prefix가 ghs_ 인 경우 GitHub App 으로 분류된다 — env에 owner PAT가
    동시에 존재해도 영향 없음.
    )
GITHUB_PATGH_TOKENtoken_valueenvu9   GH_TOKEN process-local injection이 우선되지 않음: TN   zraw token leaked: )_FIXTURE_OWNER_PAT_PREFIX _FIXTURE_BOT_INSTALLATION_PREFIXr   token_sourcer   installation_signaltoken_prefix_observedr2   r3   to_dict_RAW_LEAK_NEEDLES)env_with_bothprobe
serialisedneedles       r"   <test_v1_gh_token_process_local_injection_overrides_owner_patrt      s     04M "4E !88 
CEDVDVCWX8 $$,,,&&*J2A*NNNNEMMO,J# IZ'H+=fZ)HH'Ir$   c                     t        t        dt        i      } | j                  t        k(  sJ t	        d| dddd      }|j
                  t        k(  sJ |j                  d	u sJ |j                  d	u sJ y
)u  (2) gh auth status는 JonghyukJeon (owner_pat) 이지만, merge 실행 직전
    GH_TOKEN 으로 installation token이 주입된 상황 → audit record가
    token_source=GITHUB_APP_INSTALLATION_TOKEN 으로 기록되어야 한다.

    회장 §본질: gh CLI의 stored auth는 owner PAT 일 수 있지만, 자동화의 진짜
    실행 토큰은 process-local GH_TOKEN injection이다. audit는 후자를 기록한다.
    rd   re   r?   r@   rA   rG   rE   	pr_numbertoken_probemergedBy_loginmergedBy_typemerge_commit_sharH   FN)r   rj   rk   r   r   token_source_usedowner_pat_usedautonomy_capability_gap)rq   records     r"   >test_v2_gh_auth_owner_but_merge_records_app_installation_tokenr      s     "48
E !88886CF ##'>>>>  E)))))U222r$   c            	        t         d   d   dk(  sJ t         d   d   du sJ t         d   d   dk(  sJ t        t        d	t        i
      } t        t         d   | t         d   d   t         d   d   t         d   d   t         d   d         }|j                  t
        k(  sJ |j                  du sJ |j                  du sJ |j                  du sJ |j                  du sJ t        d|      }|j                  dk(  sJ |j                  dk(  sJ y)u:  (3) PR #73 fixture replay (mergedBy=app/jeon-jonghyuk-taskctl-bot,
    mergeCommit=b7a37521..., is_bot=true) → autonomy_capability_gap=False,
    expected_bot_identity=True, owner_pat_used=False = BOT_MERGE_IDENTITY_SUCCESS.

    회장 §본질: 본 task의 회귀 핵심 — task-2522 success path 박제.
    rM   rB   r@   rD   TrO   rJ   rG   rd   re   rL   rC   rH   rv   F   previous_scorer   r,   	   N)rP   r   rj   r   r|   r   mergedBy_is_botexpected_bot_identityr~   r}   r   delta	new_score)rq   r   r   s      r"   Gtest_v3_pr_73_mergedBy_app_bot_classified_as_bot_merge_identity_successr      sN    $W-1PPPP$X.$666!-02   "49:E  )$Z09#J/7&w/>g&y1F ##'>>>>!!T)))''4///))U222  E)))(&IE;;!??ar$   c            	        dt         fdt        fdt        fdt        fdt        fg} | D ]  \  }}t        t        dt        i      }t        |||d   d	   |d   d
   |d   d         }|j                  t        k(  s
J d|        |j                  du s
J d|        |j                  du s
J d|        |j                  du s
J d|        |j                  du rJ d|         t        t         t        t        t        t        d      }t        ddg d|      }|j                   du sJ |j"                  du sJ t%        |      t&        j(                  k(  sJ y)u%  (4) PR #68~#72 5건 fixture replay (mergedBy=JonghyukJeon) → 모두
    owner_pat fallback (autonomy_capability_gap=True, owner_pat_used=True).

    회장 §본질: task-2522 직전 5 연속 owner_pat 패턴이 회귀로 고정되어
    "성공 path"로 위장되지 않음을 박제.
    rQ   rU   rW   rY   r[   rc   re   rM   rB   rC   rO   rH   rw   rx   ry   rz   rH   zPR #TF)rQ   rU   rW   rY   r[   zJeon-Jonghyukdev_workspace)r9   N)rT   rV   rX   rZ   r]   r   ri   r   r|   r   r}   r~   r   r   r>   r    fallback_to_owner_token_detectedbot_can_merge_as_appr   r   AUTOMATION_CAPABILITY_GAP)fixturespr_numfixrq   r   r9   identitys          r"   Ctest_v4_pr_68_to_72_mergedBy_owner_classified_as_owner_pat_fallbackr     s    
]	]	]	]	]H   F%189
 $z?73j/&1L+
 ''+AART&?RA$$,=VHo=,--5FfXF5%%.?$vh?.++u4EVHoE4!F& !}-}" F (*>vH 44<<<((E111"8,0W0WWWWr$   c                    d} d}| |fD ]  }t        |d|i      }t        d|ddd	      }t        j                  |j	                         d
      }d|vs
J d|       d|vsJ |dd |v sJ t
        dz  dz  dz  }|j                         r|j                          t        ||       |j                  d      }d|vsJ d|vsJ t        d|      }t        j                  |j                        }	d|	vsJ d|	vsJ |j                  d        t
        dz  dz  j                  d      }
t        j                  d|
      rJ d       t        j                  d|
      rJ d        y)!u   (5) audit record + JSONL 라인 + delta 출력 전부에서 raw token suffix
    노출 0건. 정적 grep + JSON 직렬화 검사.

    회장 §보안: token raw value 출력 절대 금지. prefix 5자 + sha256 첫 8 hex만.
    0ghs_PUBLICprefixULTRASECRETappBotSuffixDoNotLeak2ghp_PUBLICprefixULTRASECRETownerPatSuffixDoNotLeakrd   re     r@   rA   (deadbeefdeadbeefdeadbeefdeadbeefdeadbeef)rw   rx   ry   rz   r{   F)ensure_asciiULTRASECRETzraw token leaked into record: 	DoNotLeakNrh   memoryeventsztest_2523_v5_tmp.jsonl
audit_pathutf-8encodingr   r   T
missing_okutilszbot_merge_identity.pyzghs_[A-Za-z0-9]{30,}z!long ghs_ token literal in sourcezghp_[A-Za-z0-9]{30,}z!long ghp_ token literal in source)r   r   r2   r3   rn   _WORKTREE_ROOTexistsunlinkr   	read_textr   __dict__research)secret_token_appsecret_token_pattokrq   r   rr   tmpliner   
delta_jsonsrcs              r"   5test_v5_no_raw_token_value_in_any_audit_or_log_outputr   A  s    OP "23 $%#J;LM#:+
 ZZ 0uE
J._2PQ[P^0__.*,,,2Aw*$$$ x'(25MM::<JJL6c2}}g}.D((($&&&,AfMZZ/
J...*,,,

d
#;$@ G#&==
H
HRY
H
ZCyy0#6[8[[6yy0#6[8[[66r$   c                 <   g dfd} t        d|       }|d   dk(  sJ t              dk(  sJ d       d   }t        |      }|d   du sJ |d	   du sJ |d
   du sJ |d   g k(  sJ |d   du sJ d|vsJ d|vsJ d|vsJ g d}t        |      }|d   du sJ y)u  (6) `gh pr merge --squash --delete-branch` 한 번의 호출 안에서 머지+삭제가
    수행되므로, GH_TOKEN process-local injection이 branch cleanup에도 그대로
    상속된다. PR #73 branch `task/task-2522-dev2` delete가 이 패턴으로 가능했음을
    정적으로 검증.

    회장 §본질: branch cleanup이 별도 호출 / 별도 token으로 분리되면 fallback
    위험이 생긴다. 동일 호출 안에 묶여 있어야 한다.
    c                T    ~j                  t        |              t        ddd      S )Nr   zMerged.r-   appendlistr#   )r   r5   captureds     r"   r9   zLtest_v6_branch_cleanup_inherits_bot_token_in_same_merge_call.<locals>.runner  s%    T
#!Y##r$   r?   r   r   r,   u0   merge+cleanup은 정확히 1 호출이어야 함merge_command_presentTdelete_branch_flag_presentbranch_cleanup_in_same_callforbidden_flags_detected%token_inherits_process_local_gh_token--admin--forcerebase)r)   r*   merge73--squash--delete-branchNr   r:   r;   r<   )r   r.   r   )r9   resultissuedcleanup	pr73_args
cleanup_73r   s         @r"   <test_v6_branch_cleanup_inherits_bot_token_in_same_merge_callr   u  s    !#H$
 ""f-F,1$$$x=AQQQa[F 6f=G*+t333/0D88801T999-."444:;tCCC F"""F"""6!!! KI8CJ=>$FFFr$   c                 n   ddl m}  g dfd} | g d|      }|d   dk(  sJ t              dk(  sJ ddlm}m}m} t        |d	      sJ t        |d	      sJ t        |      sJ t        t        d
t        i      }t        d|dddd      }|j                  du sJ |d   dk(  r|j                  rJ y)u  (7) PR #73 (mergedBy=bot) 머지 후 post-merge smoke 와 lifecycle reconcile
    이 정상 수행됨을 fixture replay 로 박제.

    회장 §본질: bot 머지 후에도 standard automation flow (smoke + reconcile)이
    동일하게 작동해야 함 — bot 머지가 후행 단계를 깨뜨리지 않는다.

    실호출 X — runner mock으로 smoke=PASS, reconcile=APPLIED 시뮬레이션.
    r   )run_post_merge_smokec                T    ~j                  t        |              t        ddd      S )Nr   zsmoke okr-   r   )r   r5   smoke_callss     r"   smoke_runnerzZtest_v7_post_merge_smoke_and_reconcile_pass_after_bot_identity_merge.<locals>.smoke_runner  s'    4:&!Z$$r$   )python3z-czprint('smoke ok')statusPASSr,   )LifecycleStateStuckReason	reconcile__members__rd   re   r?   r@   rA   rG   rE   rv   FNr   )utils.merge_queue_executorr   r.   &utils.lifecycle_reconciliation_managerr   r   r   hasattrcallabler   rj   r   r~   )	r   r   smoke_resultr   r   r   rq   r   r   s	           @r"   Dtest_v7_post_merge_smoke_and_reconcile_pass_after_bot_identity_merger     s     @ $&K%
 (.L !V+++{q     >=111;...I "49:E  6CF ))U222!V+F4R4RRR4Rr$   c                    t         dz  dz  dz  } | j                         r| j                          t        t        dt        i      }t        d|ddd	d
      }t        ||        t        t        dt        i      }t        d|ddd      }t        ||        | j                  d      j                         j                         }t        |      dk(  sJ |D ]K  }t        j                  |      }t        D ]  }||v rJ d|d|         d|v sJ d|v sJ d|v sJ d|v rKJ  t        j                  |d         }	|	d   t        k(  sJ |	d   dk(  sJ |	d   du sJ |	d   du sJ t        j                  |d         }
|
d   t         k(  sJ |
d   dk(  sJ |
d   du sJ |
d   du sJ | j                  d      }t"        D ]  }||vrJ  | j                  d        y!)"u	  (8) audit JSONL의 마지막 라인에 4 필드 모두 존재 검증:
    token_source_used / mergedBy_login / owner_pat_used / expected_bot_identity.

    회장 §"audit JSONL에 token_source/mergedBy/owner_pat_used/expected_bot_identity
    4 필드 존재"
    r   r   ztest_2523_v8_audit_tmp.jsonlrd   re   r?   r@   rA   rG   rE   rv   r   rc   r[   rR   rS   r\   r   r   r      zmissing required field z in r|   ry   r}   r   TFr   r   N)r   r   r   r   rj   r   r   ri   r   strip
splitlinesr.   r2   loadsr   r   r   ro   )r   	probe_apprecord_success	probe_patrecord_fallbacklinesr   r7   requiredlastfirstfullrs   s                r"   7test_v8_audit_jsonl_contains_required_4_fields_contractr     ss    8
#h
.1O
OC
zz|

%49:I (6CN ~#6 &-45I )%O 37 MM7M+113>>@Eu:?? 2**T"2 	\Hw&[*A(TRYQZ([[&	\ #g---7***7***&'1112 ::eBi D#$(>>>> !^333 !T)))'(E111 JJuQx E$%)@@@@!"&EEEE!"e+++()T111 =='=*D# "T!!!" JJ$Jr$   c                 X    t        t              h dk(  sJ t        t              dk(  sJ y)u   REQUIRED_AUDIT_FIELDS_2523 는 정확히 4종.
    {token_source_used, mergedBy_login, owner_pat_used, expected_bot_identity}.
    >   ry   r}   r|   r   r'   N)setr   r.   r=   r$   r"   3test_sanity_required_audit_fields_contract_constantr   -  s7     )* /    )*a///r$   c                 f    t        t              D  ch c]  } | j                   }} d|v sJ yc c} w )uO   MergeIdentityAuditRecord.owner_pat_used 필드 존재 — task-2523 §검증 8.r}   N)_fieldsr
   name)ffield_namess     r"   5test_sanity_owner_pat_used_field_present_in_dataclassr   :  s3    #*+C#DEa166EKE{*** Fs   .c                     t        t              } | d   du sJ | d   dk(  sJ | d   du sJ t        t              }|d   du sJ |d   t        k(  sJ |d   du sJ y)u   select_merge_token_decision: GITHUB_APP_INSTALLATION_TOKEN → allow_merge=True.
    회장 §"GH_TOKEN process-local injection이 owner PAT보다 우선" 의 후속 결정.
    allow_mergeTdecisionAPP_TOKEN_OKcapability_gapFN)r   r   r   r   )decision_appdecision_pats     r"   >test_sanity_select_merge_token_decision_app_token_allows_merger  @  s     //FGL&$...
#~555()U222 //EFL&%///
#'AAAA()T111r$   c                 D    t        t              j                  d      sJ y)uH   task-2523에서 default audit path 변경 X — task-2522 결정 유지.zbot-merge-identity.jsonlN)strr	   endswithr=   r$   r"   .test_sanity_default_audit_jsonl_path_unchangedr  P  s    '(112LMMMr$   c                    t         d   dk(  sJ t         d   d   dk(  sJ t         d   d   du sJ t         d   d	   d
k(  sJ t         d   d   dk(  sJ t         d   d   t        k(  sJ t        t         d   d   t         d   d         du sJ y)u   PR #73 fixture는 BOT_MERGE_IDENTITY_SUCCESS 의 immutable replay 입력.
    회장 §"replay fixture 필수 — PR #73 (mergedBy=app/jeon-jonghyuk-taskctl-bot
    mergeCommit=b7a37521)".
    rL   r?   rM   rB   r@   rD   TrO   rJ   rG   rI   rF   rK   rC   N)rP   r   r   r=   r$   r"   7test_sanity_pr_73_fixture_immutable_bot_identity_replayr  U  s    
 "b((($W-1PPPP$X.$666!-02   !*-1GGGG!"9:>UUUU*j!'*j!&) 
  r$   c                     g d} t        |       }d|d   v sJ |d   du sJ g d}t        |      }d|d   v sJ g d}t        |      }|d	   du sJ |d
   du sJ y)u   verify_branch_cleanup_token_inheritance — admin/force 플래그 검출.
    회장 §금지 18종 중 admin override / force 적용 회귀 박제.
    )r)   r*   r   r   r   r   r   r   r   r   F)r)   r*   r   r   r   r   r   r   )r)   r*   r   r   r   r   r   N)r   )	bad_adminres	bad_force
no_cleanups       r"   Gtest_sanity_verify_branch_cleanup_token_inheritance_rejects_admin_forcer  i  s     VI
1)
<C67777675@@@UI
1)
<C67777 9J
1*
=C,-666+,555r$   c                     t        t              } | d   du sJ | d   du sJ t        di       }|j                  t        k(  sJ t	        d|dd	      }|j
                  du sJ |j                  du sJ y)
u   token_source=UNKNOWN → fail-closed (allow_merge=False, capability_gap=True).
    process-local injection이 실패한 경우의 안전망.
    r   Fr   TNre   r   r-   )rw   rx   ry   rz   )r   r   r   rk   r   r~   r}   )r   rq   r   s      r"   .test_sanity_unknown_token_source_is_failclosedr  }  s     ++?@HM"e+++$%---!d;E!5555	F ))T111  E)))r$   )r   r-   r-   )r   r/   r   r  r   r  r;   r<   )r8   zDict[int, Any])H__doc__
__future__r   r2   r   r    sysdataclassesr   r   pathlibr   typingr   r   r   __file__resolveparentr   r  pathremoveinsertutils.bot_merge_identityr	   r
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   utils.repository_policy_adapterr   r   r   r#   r>   rP   __annotations__rT   rV   rX   rZ   r]   rj   ri   ro   rt   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r=   r$   r"   <module>r      s  < #  	  
 )  " "
 h'')0077>>~#(("HHOOC'( 3~& '    
 e< 95TXY(*A!8		
!~ 
 (&EJ$2HI!~  (&EJ$2HI!~  (&EJ$2HI!~  (&EJ$2HI!~  (&EJ$2HI!~  $:  4 > ID3H# T*Xb-\h&GZ4SvH `
0+2 N
(6(*r$   