
    Ki;                    <   U d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZ ddlZ ed      Zedz  dz  Zedz  d	z  d
z  ZdZde dZeddedddgddgg i ddddZded<   	 d&	 	 	 	 	 	 	 d'dZd(dZd)dZd)dZd)dZd)d Zd)d!Zd)d"Zd)d#Zd)d$Zd)d%Z y)*u  tests/handoff/test_validate_handoff.py
validate_handoff.py CLI 검증 테스트 (task-2458 Phase 2-B)

테스트 케이스:
- test_validate_missing_handoff      — handoff JSON 미존재 → exit 1
- test_validate_schema_invalid       — schema invalid (필수 필드 누락) → exit 1
- test_validate_task_id_mismatch     — JSON task_id != --task → exit 1
- test_validate_branch_mismatch      — JSON current_branch != --branch → exit 1
- test_validate_head_sha_mismatch    — JSON head_sha != git HEAD → exit 1
- test_validate_changed_paths_violation — changed_paths가 allowed_paths 외 → exit 1
- test_validate_pending_work_too_long   — pending_work 4001자 → exit 1
- test_validate_pass                 — 모든 조건 충족 → exit 0
    )annotationsN)Pathz-/home/jay/workspace/.worktrees/task-2458-dev4scriptszvalidate_handoff.pymemoryspecshandoff-schema.jsonz	task-9010ztask/z-dev4z1.0dev4abc1234def5678z&tests/handoff/test_validate_handoff.pytests/handoff/scripts/takeover_request2026-05-05T12:00:00Zu   잔여 작업 없음)task_idschema_versionprevious_botcurrent_branchbase_shahead_shachanged_pathsallowed_pathsforbidden_pathstest_resultshandoff_reason
created_atpending_workdictVALID_HANDOFFc                    t        t        j                        }t        |      |d<   |r|j	                  |       t        j                  dt        t              g| z   t        t              ddd|      S )u"   validate_handoff.py 실행 헬퍼.WORKSPACE_ROOTpython3T   )cwdcapture_outputtexttimeoutenv)	r   osenvironstrupdate
subprocessrunVALIDATE_SCRIPTWORKTREE)argsworkspace_root	extra_envr'   s       :/home/jay/workspace/tests/handoff/test_validate_handoff.py_run_validater4   :   sg     rzz
C/C

9>>	C()D0M     c                D   | dz  dz  }|j                  dd       | dz  dz  }|j                  dd       |dz  }|j                         s#|j                  t        j	                                || dz  }|j                  t        j                  |dd	
      d       |S )uB   tmp workspace 내에 handoff JSON 파일 기록 후 경로 반환.r   handoffsTparentsexist_okr   r   .jsonF   ensure_asciiindentutf-8encoding)mkdirexistswrite_bytesSCHEMA_PATH
read_bytes
write_textjsondumps)	workspacer   datahdirspec_dir
schema_dsthpaths          r3   _write_handoffrQ   N   s    x*,DJJtdJ+ 8#g-HNN4$N/11J{5578gYe$$E	TZZ5CgVLr5   c                \   | dz  dz  }|j                  dd       |dz  j                  t        j                                t	        dt
        g|       }|j                  }d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                   d|j                         dz   d|iz  }t!        t        j"                  |            dx}x}}g }d}|j                  }	||	v }|}
|s=d}|j                  }|j$                  } |       }||v }|}
|sd}|j                  }||v }|}
|
snt        j                  d|fd||	f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      dz  }dd|iz  }|j'                  |       |sjt        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                        t        j                        t        j                  |      dz  }dd|iz  }|j'                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d d!|iz  }|j'                  |       t        j(                  |d      i z  }t        j                  d"|j                         d#z   d$|iz  }t!        t        j"                  |            dx}
x}x}x}x}	x}x}x}x}x}x}x}}y)%u/   handoff JSON이 존재하지 않을 때 exit 1.r   r   Tr8   r   --taskr1      ==z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)sresultpy0py2py5u-   handoff 없는데 exit 0 (비정상)
stdout: 	
stderr: 
>assert %(py7)spy7Nu   없음z	not foundFAILinz.%(py3)s in %(py7)s
{%(py7)s = %(py5)s.stderr
}py3r]   r`   %(py9)spy9zh%(py12)s in %(py20)s
{%(py20)s = %(py18)s
{%(py18)s = %(py16)s
{%(py16)s = %(py14)s.stderr
}.lower
}()
}py12py14py16py18py20%(py22)spy22z2%(py25)s in %(py29)s
{%(py29)s = %(py27)s.stderr
}py25py27py29%(py31)spy31u    에러 메시지 없음
stderr: 
>assert %(py34)spy34)rC   rE   rF   rG   r4   TEST_TASK_ID
returncode
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgstdoutstderrAssertionError_format_explanationlowerappend_format_boolop)tmp_pathrN   rY   @py_assert1@py_assert4@py_assert3@py_format6@py_format8@py_assert2@py_assert6@py_assert0@py_assert11@py_assert15@py_assert17@py_assert19@py_assert13@py_assert24@py_assert28@py_assert26@py_format10@py_format21@py_format23@py_format30@py_format32@py_format33@py_format35s                             r3   test_validate_missing_handoffr   c   s    ("W,HNN4$N/%%22;3I3I3KLHl3HMF  !                    !"    9zRXR_R_Q`a    8 v}} 8}$  v}} }7J7J 7J7L 7L(L PV Z`ZgZg PVZgPg   8}                  %     7L    )4      8>    8>    8E    8K    8M     PVZg    QW      [a    [a    [h        ,FMM?;       r5   c                v   t         ddd}t        | t         |       t        dt         g|       }|j                  }d}||k(  }|st	        j
                  d|fd||f      d	t        j                         v st	        j                  |      rt	        j                  |      nd	t	        j                  |      t	        j                  |      d
z  }t	        j                  d|j                   d|j                         dz   d|iz  }t        t	        j                  |            dx}x}}|j                  |j                  z   }g }d}	|	|v }|}
|s%d}|j                  } |       }||v }|}
|sd}||v }|}
|
s2t	        j
                  d|fd|	|f      t	        j                  |	      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }|j!                  |       |sBt	        j
                  dfdf      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                        t	        j                  |      dz  }dd|iz  }|j!                  |       |st	        j
                  dfd|f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      nddz  }dd|iz  }|j!                  |       t	        j"                  |d      i z  }t	        j                  d |j                         d!z   d"|iz  }t        t	        j                  |            dx}
x}x}	x}x}x}x}x}x}}y)#uP   필수 필드가 누락된 handoff JSON → schema validation 실패 → exit 1.	interruptr   )r   r   r   rS   rT   rU   rV   rX   rY   rZ   u$   schema invalid인데 exit 0
stdout: r^   r_   r`   Nra   schemaSchemarb   )z%(py3)s in %(py5)scombined)rf   r]   z%(py7)s)zJ%(py10)s in %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py12)s.lower
}()
})py10rk   rl   rm   %(py18)srn   z%(py21)s in %(py23)spy21py23%(py25)srt   u*   스키마 에러 메시지 없음
stderr: 
>assert %(py28)spy28)r{   rQ   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   )r   invalid_datarY   r   r   r   r   r   r   r   r   @py_assert9r   r   r   @py_assert20@py_assert22@py_format17@py_format19@py_format24@py_format26@py_format27@py_format29s                          r3   test_validate_schema_invalidr   w   s>     %,	L 8\<8Hl3HMF  !                    !"    0jX     }}v}},H6 6X  X^^ ^-= -=!=  XAU   6X                   -=    "*      .6    .6    .<    .>     X    BJ      NV    NV        6fmm_E      r5   c                   t        t              }d|d<   d|d<   t        | t        |       t	        dt        g|       }|j
                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }t        j                  d|j                   d|j                         dz   d|iz  }t        t        j                   |            dx}x}}g }d}|j                  }	||	v }|}
|s*d}|j                  }||v }|}
|sd}|j                  }||v }|}
|
sFt        j                  d|fd||	f      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |	      dz  }dd|iz  }|j#                  |       |sBt        j                  dfdf      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }|j#                  |       |st        j                  dfdf      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }|j#                  |       t        j$                  |d      i z  }t        j                  d |j                         d!z   d"|iz  }t        t        j                   |            dx}
x}x}x}x}	x}x}x}x}x}}y)#u)   JSON task_id != --task 인수 → exit 1.z	task-9999r   ztask/task-9999-dev4r   rS   rT   rU   rV   rX   rY   rZ   u'   task_id 불일치인데 exit 0
stdout: r^   r_   r`   N	   불일치ra   rb   rd   re   rg   rh   )z2%(py12)s in %(py16)s
{%(py16)s = %(py14)s.stderr
})rk   rl   rm   r   rn   )z2%(py21)s in %(py25)s
{%(py25)s = %(py23)s.stderr
})r   r   rt   z%(py27)sru   u2   task_id 불일치 에러 메시지 없음
stderr: z
>assert %(py30)spy30)r   r   rQ   r{   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   )r   rL   rY   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   @py_format28r   @py_format31s                           r3   test_validate_task_id_mismatchr      s^   D!DO2D	8\40Hl3HMF  !                    !"    36==/FMM?[    9  9%   )E  SYS`S` S`I`   9                  &         *5      9?    9?    9F     S`    JP      TZ    TZ    Ta        >fmm_M       r5   c                (   t        t              }d|d<   t        | t        |       t	        dt        ddg|       }|j
                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }t        j                  d|j                   d|j                         dz   d|iz  }t        t        j                   |            dx}x}}g }d}|j                  }	|	j"                  }
 |
       }||v }|}|s*d}|j                  }||v }|}|sd}|j                  }||v }|}|snt        j                  d|fd||f      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |	      t        j                  |
      t        j                  |      dz  }dd|iz  }|j%                  |       |sBt        j                  dfdf      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd|iz  }|j%                  |       |st        j                  dfdf      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }dd |iz  }|j%                  |       t        j&                  |d      i z  }t        j                  d!|j                         d"z   d#|iz  }t        t        j                   |            dx}x}x}x}x}	x}
x}x}x}x}x}x}}y)$u2   JSON current_branch != --branch 인수 → exit 1.ztask/task-9010-dev4r   rS   z--branchztask/task-9010-dev9rT   rU   rV   rX   rY   rZ   u&   branch 불일치인데 exit 0
stdout: r^   r_   r`   Nbranchr   ra   rb   zb%(py3)s in %(py11)s
{%(py11)s = %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.stderr
}.lower
}()
}rf   r]   r`   rh   py11%(py13)spy13z2%(py16)s in %(py20)s
{%(py20)s = %(py18)s.stderr
}rm   rn   ro   rp   rq   rr   rs   rw   rx   u1   branch 불일치 에러 메시지 없음
stderr: ry   rz   r   r   rQ   r{   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   rL   rY   r   r   r   r   r   r   r   @py_assert8@py_assert10r   r   r   r   r   r   r   @py_format12@py_format14r   r   r   r   r   r   s                              r3   test_validate_branch_mismatchr      s   D2D	8\40	<-BCF   !                    !"    2&--
6==/Z    8 v}} }** *, 8,,  v}} }0L PV Z`ZgZg PVZgPg   8,                  %    +    -     }    1<      @F    @F    @M     PVZg    QW      [a    [a    [h        =V]]OL       r5   c           	     ~   | dz  }|j                          t        j                  g dt        |      dd       t        j                  g dt        |      dd       t        j                  g dt        |      dd       |dz  j	                  d       t        j                  g d	t        |      dd       t        j                  g d
t        |      dd       t        j                  dddt
        gt        |      dd       |dz  j	                  d       t        j                  g d	t        |      dd       t        j                  g dt        |      dd       t        j                  g dt        |      dd      j                  j                         }t        t              }d|d<   t
        |d<   |dz  dz  }|j                  dd       |dz  j                  t        j                                |dz  dz  }|j                  dd       |t         dz  }|j	                  t        j                  |dd      d        t!        d!t        d"g|#      }|j"                  }d$}	||	k(  }
|
st%        j&                  d%|
fd&||	f      d't)        j*                         v st%        j,                  |      rt%        j.                  |      nd't%        j.                  |      t%        j.                  |	      d(z  }t%        j0                  d)|j                   d*|j2                   d+| d,      d-z   d.|iz  }t5        t%        j6                  |            d/x}x}
}	g }d}|j2                  }||v }	|	}|	s=d0}|j2                  }|j8                  } |       }||v }|}|sd1}|j2                  }||v }|}|snt%        j&                  d2|	fd3||f      t%        j.                  |      d't)        j*                         v st%        j,                  |      rt%        j.                  |      nd't%        j.                  |      d4z  }d5d6|iz  }|j;                  |       |	sjt%        j&                  d2fd7f      t%        j.                  |      d't)        j*                         v st%        j,                  |      rt%        j.                  |      nd't%        j.                        t%        j.                        t%        j.                  |      d8z  }d9d:|iz  }|j;                  |       |st%        j&                  d2fd;f      t%        j.                  |      d't)        j*                         v st%        j,                  |      rt%        j.                  |      nd't%        j.                  |      d<z  }d=d>|iz  }|j;                  |       t%        j<                  |d$      i z  }t%        j0                  d?|j2                         d@z   dA|iz  }t5        t%        j6                  |            d/x}x}x}x}	x}x}x}x}x}x}x}x}}y/)Bu   JSON head_sha가 실제 git rev-parse 결과와 불일치 → exit 1.

    임시 git repo를 tmp_path 내에 만들어 HEAD SHA를 제어한다.
    repo)gitinitz--initial-branch=mainT)r#   checkr$   )r   configz
user.emailztest@test.com)r   r   z	user.nameTestzinit.txtr   )r   add.)r   commit-mr   r   checkoutz-bz
change.txtchange)r   r   r   r   )r   z	rev-parseHEAD)r#   r$   r%   0000000r   r   r   r   r8   r   r7   r;   Fr<   r=   r@   rA   rS   z--check-head-sharT   rU   rV   rX   rY   rZ   u(   head_sha 불일치인데 exit 0
stdout: r^   z

real_sha=z, recorded=0000000r_   r`   Nshara   rb   rd   re   rg   rh   ri   rj   rp   rq   rr   rs   rw   rx   u.   SHA 불일치 에러 메시지 없음
stderr: ry   rz   )rC   r,   r-   r*   rH   TEST_BRANCHr   stripr   r   rE   rF   rG   r{   rI   rJ   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   )r   r   real_sharL   rN   rM   rP   rY   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s                                  r3   test_validate_head_sha_mismatchr      sz    fDJJLNN;TRV"&(NNCTZ^"&(NN9s4yPT"&(	J""6*NN&CITRVWNN2D	"&( NN	
D+.CITZ^ 
L$$X.NN&CITRVWNN4#d)4"&( ~~$#d)DtfUUW 
 D D(D	 h(HNN4$N/%%22;3I3I3KL(?Z'DJJtdJ+l^5))E	TZZ5CgV	<!34F   !                    !"    4FMM?*V]]O
hZ1	3    :  :& % 6== =3F3F 3F3H %3H*H F V\VcVc FVcLc   :                    '     %3H    +0      4:    4:    4A    4G    4I     FVc    MS      W]    W]    Wd        :&--I       r5   c                4   t        t              }dg|d<   ddg|d<   t        | t        |       t	        dt        g|       }|j
                  }d}||k(  }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                   d|j                         dz   d|iz  }t        t        j                   |            dx}x}}g }d}|j                  }	|	j"                  }
 |
       }||v }|}|s*d}|j                  }||v }|}|sd}|j                  }||v }|}|snt        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      t        j                  |      dz  }dd|iz  }|j%                  |       |sBt        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j%                  |       |st        j                  dfdf      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d d!|iz  }|j%                  |       t        j&                  |d      i z  }t        j                  d"|j                         d#z   d$|iz  }t        t        j                   |            dx}x}x}x}x}	x}
x}x}x}x}x}x}}y)%uC   changed_paths에 allowed_paths 범위 밖 파일 포함 → exit 1.r   r   ztests/handoff/test_foo.pyzscripts/dangerous_script.pyr   rS   rT   rU   rV   rX   rY   rZ   u*   changed_paths 위반인데 exit 0
stdout: r^   r_   r`   Nallowed   초과ra   rb   r   r   r   r   r   r   rp   rq   rr   rs   rw   rx   u.   경로 위반 에러 메시지 없음
stderr: ry   rz   r   r   s                              r3   %test_validate_changed_paths_violationr      s   D-.D#%D 8\40Hl3HMF  !                    !"    6fmm_Jv}}o^    9  ++ +- 9--  V]] ]1J f X^XeXe fXeNe   9-                  &    ,    .     ]    2:      >D    >D    >K     fXe    OU      Y_    Y_    Yf        :&--I       r5   c                b   t        t              }d|d<   | dz  dz  }|j                  dd       |dz  j                  t        j                                | dz  dz  }|j                  dd       |t         d	z  }|j                  t        j                  |d
      d       t        dt        g|       }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |      dz  }	t        j$                  d|j&                   d|j(                         dz   d|	iz  }
t+        t        j,                  |
            dx}x}}y)uR  pending_work가 4001자이면 validate에서 FAIL → exit 1.

    스키마(maxLength=4000) 또는 validate_handoff 자체 검사 중 하나가 잡아야 한다.
    스키마 검사는 JSON 파일 내용 기반이므로, validate_handoff.py 에서 검사하는 경우
    파일에 4001자가 직접 기록된 JSON을 제공한다.
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxr   r   r   Tr8   r   r7   r;   F)r>   r@   rA   rS   rT   rU   rV   rX   rY   rZ   u*   pending_work 4001자인데 exit 0
stdout: r^   r_   r`   N)r   r   rC   rE   rF   rG   r{   rH   rI   rJ   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   )r   rL   rN   rM   rP   rY   r   r   r   r   r   s              r3   #test_validate_pending_work_too_longr     s    D &D ("W,HNN4$N/%%22;3I3I3KLh+DJJtdJ+l^5))E	TZZ59GLHl3HMF  !                    !"    6fmm_Jv}}o^     r5   c                   t        t              }t        | t        |       t	        dt        g|       }|j
                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                   d	|j                         d
z   d|iz  }t        t        j                   |            dx}x}}	 t#        j$                  |j                        }j,                  }d} ||      }
|
t        k(  }|s6t        j                  d|fd|
t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |
      dt        j                         v st        j                  t              rt        j                  t              nddz  }t        j                  d|j-                  d             dz   d|iz  }t        t        j                   |            dx}x}x}
}y# t"        j&                  $ r0}	t)        j*                  d|	 d|j                          Y d}	~	d}	~	ww xY w)u9   유효한 handoff JSON → exit 0, stdout에 JSON 출력.rS   rT   r   rV   rX   rY   rZ   u'   유효한 handoff인데 exit 1
stdout: r^   r_   r`   Nu$   stdout이 유효한 JSON이 아님: z	
stdout: r   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py8)sout_datar{   )r[   r\   py4py6py8u"   출력 JSON의 task_id 불일치: z
>assert %(py10)sr   )r   r   rQ   r{   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   rI   loadsJSONDecodeErrorpytestfailget)r   rL   rY   r   r   r   r   r   r   e@py_assert5@py_assert7@py_format9@py_format11s                 r3   test_validate_passr   .  ss   D8\40Hl3HMF  !                    !"    36==/FMM?[    Y::fmm, << 	 <	" "l2   "l                  "    #      '3    '3    -X\\)-D,EF       Y:1#ZWXXYs   -J- -K0 %K++K0c                   | dz  dz  }|j                  d       |t         dz  }|j                  dd       t        t              }g d	|d
<   dg|d<   t        | t        |       t        dt        g|       }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d|j                   d|j                          dz   d|iz  }	t#        t        j$                  |	            dx}x}}|j                   |j                  z   }
g }d}|
j&                  } |       }||v }|}|sd}||
v }|}|sd}||
v }|}|s(t        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |
      rt        j                  |
      ndt        j                  |      t        j                  |      dz  }d d!|iz  }|j)                  |       |st        j                  dfd"|
f      t        j                  |      dt        j                         v st        j                  |
      rt        j                  |
      ndd#z  }d$d%|iz  }|j)                  |       |st        j                  dfd&|
f      t        j                  |      dt        j                         v st        j                  |
      rt        j                  |
      ndd'z  }d(d)|iz  }|j)                  |       t        j*                  |d      i z  }t        j                  d*|
       d+z   d,|iz  }t#        t        j$                  |            dx}x}x}x}x}x}x}x}x}}y)-u   handoff allowed_paths가 task 파일 allowed_resources를 초과(forge)하면 → exit 1.

    capability snapshot 교차검증: 봇이 handoff JSON에 allowed_paths를 임의로
    넓혀 적어 권한을 우회하는 시도를 차단한다.
    r   tasksT)r9   z.mdut   # task-9010 — test
```yaml
allowed_resources:
  paths:
    - "scripts/"
  forbidden_paths:
    - ".github/**"
```
r@   rA   )r   z.github/workflows/zsecrets/r   zscripts/x.pyr   rS   rT   rU   rV   rX   rY   rZ   u   forgery인데 exit 0
stdout: r^   r_   r`   Nforgeryr   u   task 파일rb   )zD%(py3)s in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.lower
}()
}r   )rf   r]   r`   rh   z%(py11)sr   )z%(py14)s in %(py16)s)rl   rm   r   rn   r   r   r   rt   u*   forgery 에러 메시지 없음
combined: r   r   )rC   r{   rH   r   r   rQ   r4   r|   r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   )r   task_dirtask_mdrL   rY   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s                              r3   )test_validate_capability_snapshot_forgeryr  D  sP    ("W,HNN4N L>--G	    DJD+,D8\40Hl3HMF  !                    !"    )z&--Q     }}v}},H@@^^@#%@%%@@x@ @ H$@ @ /@.?	@%@ @ 7@i 	@ @ :@	@ @ (@'? @ @ 7@i @ @ 7@i $@ @ 7@i &@ @ @ :@	@ :@	@ /@.?	@x@ @ 7@i @ @ :@	@ @ (@'?  @ @ 7@i  @ @ @ :@	@ :@	@ /@.?	@ H@ @ 7@i @ @ :@	@ @ (@'? %@ @ 7@i %@ @ @ :@	@ :@	@ 2@	@ @ /@.?	4XJ?	@ @ @ -@,?	@ @ @ @r5   )N)r0   z	list[str]r1   r   r2   zdict | Nonereturnzsubprocess.CompletedProcess)rK   r   r   r*   rL   r   r  r   )r   r   )!__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter}   rI   r(   r,   pathlibr   r   r/   r.   rF   r{   r   r   __annotations__r4   rQ   r   r   r   r   r   r   r   r   r   r5   r3   <module>r     s   #    	   
 ?@Y&)>>!G+.CC l^5) !>?&
3((*t 0 "
  !	(*(.(,9@,@,&@r5   