
    FiCD                       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ZddlZddlmZ ddlZ ee      j'                         j(                  d   Zedz  dz  Zedz  dz  Zd d	Z ed
d      Z eed       Zej8                  j;                  ed      Zh dZd!dZ ed"d       Z!ed"d       Z"ed"d       Z#ed"d       Z$ed"d       Z%ed"d       Z&ed"d       Z'ed"d       Z(d#dZ)dZ*d"dZ+d"dZ,d#dZ-d#dZ.d"dZ/y)$u  tests/regression/test_empty_escalation_marker.py — task-2471+1 F4 회귀 테스트 (Test 2).

``check_escalation_marker_payload`` 함수와 done-watcher.sh / finish-task.sh 통합 검증.

토르(개발1팀)의 수정이 .done.escalated marker에 올바른 JSON payload를 박제하는지 영구 차단.

함수 시그니처:
    check_escalation_marker_payload(task_id: str, *, events_dir: Optional[str] = None) -> dict

각 반환값: {"ok": bool, "reason": str, "detail": dict}
fail-closed 패턴.

A. silent_corruption_guard 측 check_escalation_marker_payload 단위 테스트 (7건)
B. done-watcher.sh inline python 통합 검증 (1건+)
C. finish-task.sh ESCALATED 분기 검증 (2건)

헤임달(개발2팀 테스터) 작성 — task-2471+1 F4.
    )annotationsN)Path   scriptszdone-watcher.shzfinish-task.shc                <   t         |z  }t        j                  j                  | t	        |            }||j
                  t        d|       t        j                  j                  |      }|t        j                  | <   |j
                  j                  |       |S )u8   절대 경로로 모듈 로드 (sys.path 오염 방지).zcannot load spec for )	WORKSPACE	importlibutilspec_from_file_locationstrloaderImportErrormodule_from_specsysmodulesexec_module)mod_namefile_rel	file_pathspecmodules        D/home/jay/workspace/tests/regression/test_empty_escalation_marker.py_load_moduler   &   s    H$I>>11(C	NKD|t{{*1)=>>^^,,T2F"CKKKKF#M    %silent_corruption_guard_payload_aliasz utils/silent_corruption_guard.pycheck_escalation_marker_payloaduC   check_escalation_marker_payload 미구현 (토르 작업 미완료))reason>   tshostr   sourcetrigger	done_pathage_secondsc                <    | | dz  }|j                  |d       |S )u5   events_dir에 <task_id>.done.escalated 파일 생성..done.escalatedutf-8encoding)
write_text)
events_dirtask_idcontentps       r   _write_escalatedr.   D   s(    y00ALL7L+Hr   c                   t         j                  dt        |             }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}|d	   }d
}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)uX   .done.escalated 파일 없으면 ok=True (아직 에스컬레이션 미발생 = 정상).z	task-8001r*   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresultdict)py0py1py2py4NokTisz%(py1)s is %(py4)sr5   r7   assert %(py6)spy6)scgr   r   r1   r3   @py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanation_call_reprcompare)tmp_pathr2   @py_assert3@py_format5@py_assert0@py_assert2@py_format7s          r   test_payload_file_absent_okrN   R   s     00X0WFfd########:###:######f###f######d###d##########$<4<4<4<4r   c                   t        | dd       t        j                  dt        |             }|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}d}|d   }|j                  } |       }||v }|st	        j
                  d|fd||f      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }	t	        j                  d|d         dz   d|	iz  }
t        t	        j                  |
            dx}x}x}x}}|j                  di       }|j                  }d} ||      }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }t	        j                  d|j                  d            dz   d|iz  }t        t	        j                  |            dx}x}x}x}}y)uN   0 bytes .done.escalated → ok=False, reason에 'empty' 포함, detail.size=0.z	task-8002 r,   r0   r8   Fr9   r;   r<   r=   r>   Nemptyr   inzD%(py1)s in %(py8)s
{%(py8)s = %(py6)s
{%(py6)s = %(py4)s.lower
}()
}r5   r7   r>   py8u   reason에 'empty' 없음: 
>assert %(py10)spy10detailsizer   ==)zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)s)r4   r6   r7   r>   py9zdetail.size != 0: z
>assert %(py11)spy11)r.   r?   r   r   rB   rG   rD   rE   rF   lower_format_assertmsggetr@   rA   rC   )rH   r2   rK   rI   rL   rJ   rM   @py_assert5@py_assert7@py_format9@py_format11rZ   @py_assert1@py_assert8@py_format10@py_format12s                   r   test_payload_empty_file_failrk   ]   s    X{B700X0WF$< 5 <5    <5   <   5        fX& &,, ,. 7..  7.        '    -    /    %VH%5$89      ZZ"%F::OfO:fOO"OOOOOOOOO6OOO6OOO:OOOfOOOOOOOOO&8F9K8N$OOOOOOOOr   c                   t        | dd       t        j                  dt        |             }|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}d}|d   }|j                  } |       }||v }|st	        j
                  d|fd||f      t	        j                  |      t	        j                  |      t	        j                  |      t	        j                  |      dz  }	t	        j                  d|d         dz   d|	iz  }
t        t	        j                  |
            dx}x}x}x}}y)uI   비-JSON 텍스트 .done.escalated → ok=False, reason에 'JSON' 포함.z	task-8003zhello world not jsonrQ   r0   r8   Fr9   r;   r<   r=   r>   Njsonr   rS   rU   rV   u   reason에 'JSON' 없음: rX   rY   )r.   r?   r   r   rB   rG   rD   rE   rF   r`   ra   )rH   r2   rK   rI   rL   rJ   rM   rc   rd   re   rf   s              r   test_payload_non_json_failrn   n   sN    X{4JK00X0WF$< 5 <5    <5   <   5        VH% %++ +- 6--  6-        &    ,    .    $F8$4#78     r   c                `   t        | dd       t        j                  dt        |             }|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}|d   j                         }g }d}||v }	|	}|	s&d}
|
|v }|}|sd}||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dz  }dd|iz  }|j                  |       |	s8t	        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                  |       |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         d+z   d,|iz  }t        t	        j                  |            dx}x}x}x}	x}
x}x}x}x}x}x}}y)-u6   JSON array .done.escalated → ok=False (dict 아님).z
task-8004az	[1, 2, 3]rQ   r0   r8   Fr9   r;   r<   r=   r>   Nr   missingrequiredr3   objectinvalidrS   )z%(py3)s in %(py5)sreason_lower)py3py5z%(py7)spy7)z%(py10)s in %(py12)s)rY   py12z%(py14)spy14)z%(py17)s in %(py19)s)py17py19z%(py21)spy21)z%(py24)s in %(py26)s)py24py26z%(py28)spy28)z%(py31)s in %(py33)s)py31py33z%(py35)spy35   u   reason이 부적절: z
>assert %(py38)spy38)r.   r?   r   r   rB   rG   rD   rE   rF   r`   r@   rA   rC   append_format_boolopra   )rH   r2   rK   rI   rL   rJ   rM   rt   rg   @py_assert4@py_assert9@py_assert11@py_assert16@py_assert18@py_assert23@py_assert25@py_assert30@py_assert32@py_format6@py_format8@py_format13@py_format15@py_format20@py_format22@py_format27@py_format29@py_format34@py_format36@py_format37@py_format39s                                 r   test_payload_json_list_failr   }   s    X|[A00#h-0XF$< 5 <5    <5   <   5       (#))+L44\!44%4 4 \!4 	4 |#	4
 4
 $4 4 #4"34\4 4 +4) 	4 4 .4V4 4 43 "4 4 +4) "4 4 4 .4V4 .44 #4"344 4 +4)	 4 4 .4V4 4 43	 &4 4 +4)	 &4 4 4 .4V4 .44 #4"34 \4 4 +4) 4 4 .4V4 4 43 "4 4 +4) "4 4 4 .4V4 .44 #4"34 |	4 4 +4) 	4 4 .4V4 4 43 $	4 4 +4) $	4 4 4 .4V4 .44 #4"34
 4 4 +4) 4 4 .4V4 4 43 %4 4 +4) %4 4 4 .4V4 .44 &4^4 4 #4"3	vh/234 4 4 !4 34 4 4 4 4r   c                ^   t        | dd       t        j                  dt        |             }|d   }d}||u }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      d	z  }d
d|iz  }t        t	        j                  |            dx}x}}y)u>   JSON scalar string .done.escalated → ok=False (dict 아님).z
task-8004bz"x"rQ   r0   r8   Fr9   r;   r<   r=   r>   N)	r.   r?   r   r   rB   rG   rD   rE   rF   )rH   r2   rK   rI   rL   rJ   rM   s          r   test_payload_json_scalar_failr      sw     X|U;00#h-0XF$< 5 <5    <5   <   5       r   c                   ddddddd}t        | dt        j                  |             t        j	                  dt        |       	      }|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u0   trigger 키 누락 .done.escalated → ok=False.2026-05-07T00:00:00Ztesttesthost/tmp/x.donel  )r   r    r   r"   r#   r   z	task-8005rQ   r0   r8   Fr9   r;   r<   r=   r>   Nr.   rm   dumpsr?   r   r   rB   rG   rD   rE   rF   rH   payloadr2   rK   rI   rL   rJ   rM   s           r   &test_payload_dict_missing_trigger_failr      s    
 %"G X{DJJw4GH00X0WF$< 5 <5    <5   <   5       r   c                   ddddddd}t        | dt        j                  |      	       t        j	                  dt        |       
      }|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)u/   reason 키 누락 .done.escalated → ok=False. done-watcher.sh:stale_done_30minr   r   r   r   r   )r!   r   r    r   r"   r#   z	task-8006rQ   r0   r8   Fr9   r;   r<   r=   r>   Nr   r   s           r   %test_payload_dict_missing_reason_failr      s     6$"G X{DJJw4GH00X0WF$< 5 <5    <5   <   5       r   c                   dddddddd}t        | d	t        j                  |      
       t        j	                  d	t        |             }|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  di       }|j                  d      }	d}|	|u}
|
st        j                  d|
fd|	|f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      dz  }t        j                  d|      dz   d|iz  }t        t        j                  |            dx}
}t!        |	      }||	k(  }|s<t        j                  d|fd||	f      dt        j                         v st        j                  t               rt        j                  t               nddt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      dt        j                         v st        j                  |	      rt        j                  |	      nddz  }t        j                  d |	      d!z   d"|iz  }t        t        j                  |            dx}}d#}||	v }|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  }t        t        j                  |            dx}}d(}||	v }|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  }t        t        j                  |            dx}}y))uH   trigger + reason + 모든 필수 키 포함 .done.escalated → ok=True.r   r   zscripts/done-watcher.sh:96-122r   z0/home/jay/workspace/memory/events/task-8007.doner   z?stale .done unprocessed for >= 1800s; cron escalation triggered)r!   r   r    r   r"   r#   r   z	task-8007rQ   r0   r8   Tr9   r;   r<   r=   r>   NrZ   payload_keys)is not)z%(py0)s is not %(py3)sr4   ru   u   detail에 payload_keys 없음: 
>assert %(py5)srv   r\   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)ssortedr4   r5   ru   rv   u%   payload_keys가 정렬되지 않음: 
>assert %(py7)srw   r!   rS   z%(py1)s in %(py3)sr5   ru   assert %(py5)sr   )r.   rm   r   r?   r   r   rB   rG   rD   rE   rF   rb   r@   rA   rC   ra   r   )rH   r   r2   rK   rI   rL   rJ   rM   rZ   r   rg   @py_format4r   r   r   s                  r   test_payload_valid_full_okr      s    6$2GSG X{DJJw4GH00X0WF$<4<4<4<4ZZ"%F::n-L#Q<t#QQQ<tQQQQQQ<QQQ<QQQtQQQ'Fvj%QQQQQQQ, </   <                               $0    $0    0/?@     $9$$$$9$$$9$$$$$$$$$$$$$$$$#8|####8|###8######|###|#######r   c                    d} t        t        |       }|st        j                  d      dz   dt	        j
                         v st        j                  t               rt        j                  t               nddt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |       t        j                  |      dz  }t        t        j                  |            dx} }y)uV   check_escalation_marker_payload 함수가 silent_corruption_guard에 존재해야 함.r   um   check_escalation_marker_payload 함수 미존재 — 토르의 utils/silent_corruption_guard.py 수정 필요z7
>assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}hasattrr?   r   N)
r   r?   rB   ra   r@   rA   rC   rD   rE   rF   )rL   r   r   s      r   1test_smoke_check_escalation_marker_payload_existsr      s    9 739: :   	x                       :    ;     r   a  import os, sys, json, datetime, socket
path = os.environ.get('ESCALATED_PATH', '')
done_path = os.environ.get('ESCALATED_DONE_PATH', '')
age_seconds = int(os.environ.get('ESCALATED_AGE', '0'))
if not path:
    sys.exit(1)
payload = {
    'trigger': 'done-watcher.sh:stale_done_30min',
    'ts': datetime.datetime.now(datetime.timezone.utc).isoformat(),
    'source': 'scripts/done-watcher.sh:96-122',
    'host': socket.gethostname(),
    'done_path': done_path,
    'age_seconds': age_seconds,
    'reason': 'stale .done unprocessed for >= 1800s; cron escalation triggered'
}
try:
    fd = os.open(path, os.O_CREAT|os.O_EXCL|os.O_WRONLY, 0o644)
    with os.fdopen(fd, 'w', encoding='utf-8') as f:
        json.dump(payload, f, ensure_ascii=False, indent=2)
except FileExistsError:
    sys.exit(1)
c                   | dz  }|j                          d}|| dz  }|| dz  }|j                  dd       t        j                  j	                         }t        |      |d<   t        |      |d	<   d
|d<   t        j                  ddt        g|ddd      }|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|j$                         dz   d|
iz  }t'        t        j(                  |            dx}x}	}|j*                  } |       }	|	st        j                   d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      dz  }t'        t        j(                  |            dx}}	|j-                         j.                  }d}||kD  }|st        j                  d |fd!||f      d"t        j                         v st        j                  |      rt        j                  |      nd"t        j                  |      d#z  }t        j                   d$| d%      d&z   d'|iz  }
t'        t        j(                  |
            dx}}	 t1        j2                  |j5                  d            }t<        t?        jA                               z
  }| }|st        j                   d)|d*|      d+z   d,d-t        j                         v st        j                  |      rt        j                  |      nd-iz  }t'        t        j(                  |            d}|d.   }d/}	||	k(  }|slt        j                  d|fd0||	f      t        j                  |      t        j                  |	      d1z  }d2d3|iz  }t'        t        j(                  |            dx}x}}	|d4   }d5}	||	k(  }|slt        j                  d|fd0||	f      t        j                  |      t        j                  |	      d1z  }d2d3|iz  }t'        t        j(                  |            dx}x}}	|d6   }t        |      }||k(  }|st        j                  d|fd7||f      t        j                  |      d8t        j                         v st        j                  t
              rt        j                  t
              nd8d9t        j                         v st        j                  |      rt        j                  |      nd9t        j                  |      d:z  }d;d<|iz  }t'        t        j(                  |            dx}x}}|d=   }|jB                  }d>} ||      }|std?t        j                  |      t        j                  |      t        j                  |      t        j                  |      d@z  }t'        t        j(                  |            dx}x}x}}y# t0        j6                  $ r#}t9        j:                  d(|        Y d}~d}~ww xY w)Au   done-watcher.sh의 inline python이 올바른 JSON payload를 생성하는지 검증.

    결과 파일 존재 + 크기 > 0 + JSON 파싱 가능 + 모든 필수 키 포함.
    eventsztask-test-8100.doner%   doner&   r'   ESCALATED_PATHESCALATED_DONE_PATH1900ESCALATED_AGEpython3-cT
   envcapture_outputtexttimeoutr   r\   z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)sr2   r4   r6   rv   u   inline python 실패 rc=z
stdout=z
stderr=r   rw   Nu-   .done.escalated 파일이 생성되지 않음C
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}escalated_filer4   r6   r7   )>)z%(py0)s > %(py3)s	file_sizer   u.   .done.escalated 파일이 비어 있음 (size=)r   rv   u$   .done.escalated JSON 파싱 실패: u!   payload에 누락된 필수 키: u   
실제 payload: 
>assert not %(py0)sr4   missing_keysr!   r   z%(py1)s == %(py4)sr<   r=   r>   r#   r   r"   )z0%(py1)s == %(py6)s
{%(py6)s = %(py3)s(%(py4)s)
}r   	done_file)r5   ru   r7   r>   zassert %(py8)srW   r   stalezLassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
})r5   ru   rv   rw   )"mkdirr)   osenvironcopyr   
subprocessrun_DONE_WATCHER_INLINE_PY
returncoderB   rG   r@   rA   rC   rD   ra   stdoutstderrrE   rF   existsstatst_sizerm   loads	read_textJSONDecodeErrorpytestfailREQUIRED_PAYLOAD_KEYSsetkeys
startswith)rH   r*   r+   r   r   r   r2   rg   r   rI   r   r   rJ   r   rL   r   r   excr   @py_format2rK   rM   rc   re   @py_assert6s                            r   5test_done_watcher_inline_python_creates_valid_payloadr   
  s   
 H$JGy..IWI_"==N '2
**//
C/C!$YC!C^^	D12F   !                    !"    #6#4#4"5Yv}}oYW]WdWdVef    
   S "S"SS$SSSSSSS>SSS>SSS SSS"SSSSSS ##%--IW9q=WWW9qWWWWWW9WWW9WWWqWWWJ9+UVWWWWWWWB**^55w5GH
 )3w||~+>>L    ,L+;;Mg[Y              
 9C!CC!CCCCC!CCCCCCC!CCCCCCCC=!)T)!T))))!T)))!)))T)))))));13y>1>1111>11111111131113111111y111y111>111111180''00'00000000'0000000000000  B:3%@AABs   7%Z1 1['[""['c                   | dz  }|j                          d}|| dz  }|| dz  }|j                  dd       |j                  dd       t        j                  j	                         }t        |      |d	<   t        |      |d
<   d|d<   t        j                  ddt        g|ddd      }|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      dz   d|
iz  }t#        t        j$                  |            dx}x}	}|j'                  d      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd z  }t        j                   d!      d"z   d#|iz  }
t#        t        j$                  |
            dx}}y)$uX   이미 .done.escalated가 존재할 때 inline python은 rc=1로 종료 (O_EXCL 보장).r   ztask-test-8101r   r%   r   r&   r'   z{"trigger":"existing"}r   r   r   r   r   r   Tr   r   r   r\   r   r2   r   u*   O_EXCL 중복 생성 방지 실패 — rc=u    (기대값=1)r   rw   Nz
"existing"rS   r   r,   r   u*   기존 escalated 내용이 덮어씌워짐r   rv   )r   r)   r   r   r   r   r   r   r   r   rB   rG   r@   rA   rC   rD   ra   rE   rF   r   )rH   r*   r+   r   r   r   r2   rg   r   rI   r   r   r,   rK   rL   r   s                   r   6test_done_watcher_inline_python_idempotent_file_existsr   E  s
   H$JGy..IWI_"==N'26I
**//
C/C!$YC!C^^	D12F   !                    !"    5V5F5F4G~V     &&&8GP<7"PPP<7PPP<PPPPPP7PPP7PPPP$PPPPPPPr   c                    t         j                  }  |        }|st        j                  dt                dz   dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |       t        j                  |      dz  }t        t        j                  |            dx} }t        j                  ddt        t               gddd	
      }|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z   d|iz  }t        t        j                  |            dx} x}}y)u,   finish-task.sh bash syntax 검증 (bash -n).u   finish-task.sh 없음: r   FINISH_TASK_SCRIPTr   Nbash-nTr   r   r   r   r   r\   r   r2   r   zfinish-task.sh syntax error:
r   rw   )r   r   rB   ra   r@   rA   rC   rD   rE   rF   r   r   r   r   rG   r   rg   rI   rJ   r2   r   r   r   s          r    test_finish_task_sh_syntax_validr   l  sj   $$V$&V&VV*ABTAU(VVVVVVVVVVVVV$VVV&VVVVVV^^	s-./	F   !                    !"    )8     r   c                    t         j                  }  |        }|st        j                  dt                dz   dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |       t        j                  |      dz  }t        t        j                  |            dx} }t        j                  ddt        t               gddd	
      }|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z   d|iz  }t        t        j                  |            dx} x}}y)u-   done-watcher.sh bash syntax 검증 (bash -n).u   done-watcher.sh 없음: r   DONE_WATCHER_SCRIPTr   Nr   r   Tr   r   r   r\   r   r2   r   zdone-watcher.sh syntax error:
r   rw   )r   r   rB   ra   r@   rA   rC   rD   rE   rF   r   r   r   r   rG   r   r   s          r   !test_done_watcher_sh_syntax_validr   z  sj   %%Y%'Y'YY+CDWCX)YYYYYYYYYYYYY%YYY'YYYYYY^^	s./0	F   !                    !"    *&--9     r   c                8
   d}| dz  }|j                          || dz  }d| d| d}t        j                  dd|gd	d	d
      }|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z   d|	iz  }
t        t	        j                  |
            dx}x}}|j                  } |       }|st	        j                  d      dz   dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            dx}}	 t        j                   |j#                  d            }h d}|t+        j-                               z
  }| }|st	        j                  d|d|      dz   d d!t        j                         v st	        j                  |      rt	        j                  |      nd!iz  }t        t	        j                  |            d}|d"   }d#}||k(  }|slt	        j
                  d|fd$||f      t	        j                  |      t	        j                  |      d%z  }d&d'|iz  }t        t	        j                  |            dx}x}}|d(   }d)}||k(  }|slt	        j
                  d|fd$||f      t	        j                  |      t	        j                  |      d%z  }d&d'|iz  }t        t	        j                  |            dx}x}}|d*   }||k(  }|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  }	t        t	        j                  |	            dx}}d)}|d/   }||v }|slt	        j
                  d0|fd1||f      t	        j                  |      t	        j                  |      d%z  }d&d'|iz  }t        t	        j                  |            dx}x}}y# t        j$                  $ r#}t'        j(                  d|        Y d}~d}~ww xY w)2u#  finish-task.sh ESCALATED 분기의 inline python이 올바른 JSON payload를 생성하는지 검증.

    finish-task.sh의 ESCALATED 분기 inline python 코드를 직접 실행하여
    .done.escalated payload가 trigger/ts/source/state/task_id/reason 키를 포함하는지 확인.
    ztask-test-8200r   r%   zimport json, datetime, sys
payload = {
    'trigger': 'finish-task.sh:taskctl_state_escalated',
    'ts': datetime.datetime.now(datetime.timezone.utc).isoformat(),
    'source': 'scripts/finish-task.sh:449-454',
    'state': 'ESCALATED',
    'task_id': 'zl',
    'reason': 'taskctl status returned ESCALATED; .done blocked, escalation marker emitted'
}
with open('zF', 'w') as f:
    json.dump(payload, f, ensure_ascii=False, indent=2)
r   r   Tr   r   r   r\   r   r2   r   u/   finish-task.sh ESCALATED inline python 실패: r   rw   Nu    .done.escalated 파일 미생성r   escalated_pathr   r&   r'   u&   ESCALATED payload JSON 파싱 실패: >   r   stater   r    r+   r!   u   ESCALATED payload 누락 키: u	   
실제: r   r4   r   r!   z&finish-task.sh:taskctl_state_escalatedr   r<   r=   r>   r  	ESCALATEDr+   )z%(py1)s == %(py3)sr   r   rv   r   rS   )z%(py1)s in %(py4)s)r   r   r   r   rB   rG   r@   rA   rC   rD   ra   r   rE   rF   r   rm   r   r   r   r   r   r   r   )rH   r+   r*   r  finish_task_inline_pyr2   rg   r   rI   r   r   rJ   r   r   required_keysr   r   rK   rL   rM   r   s                        r   Atest_finish_task_sh_escalated_inline_python_creates_valid_payloadr    s    GH$JWI_"==N! 	    ^^	D/0	F   !                    !"    :&--I       F "F"FF$FFFFFFF>FFF>FFF FFF"FFFFFFD**^55w5GH
 NM 3w||~#66L    )(8
7+N               9I!II!IIIII!IIIIIII!IIIIIIII7*{*{****{******{*******9((((((((((((((((((((((((+'(++;+++++;++++;+++++++++++  D<SEBCCDs   -%S# #T6TT)r   r   r   r   )r*   r   r+   r   r,   r   returnr   )rH   r   r  None)r  r  )0__doc__
__future__r   builtinsr@   _pytest.assertion.rewrite	assertionrewriterB   importlib.utilr	   rm   r   r   r   pathlibr   r   __file__resolveparentsr   r   r   r   r?   r   PAYLOAD_FN_MISSINGmarkskipifSKIP_PAYLOADr   r.   rN   rk   rn   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r     s  $ #      	  
  
 N""$,,Q/	)+.?? *-== 	 +& !&GHH {{!!P " 
 b      
P 
P    4 4" ! ! ! !( ! !( $ $> 281v QN5,r   