
    +jn                    >   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ZddlZddlZddlmZmZmZ ddlmZ ddlmZ  ee      j3                         j4                  j4                  j4                  Z ee      ej:                  vr"ej:                  j=                  d ee             ddZ edd	        ed
d        edd      Z e jB                  Z!dZ"dZ#dZ$dZ%d Z&d Z'd Z( G d dejR                        Z*e+dk(  r ejX                          yy)u  task-2717 ANU_OWNED_CALLBACK_ENFORCEMENT — 필수 regression (6).

회장 verbatim: "문서/메모리 박제로 끝내지 말고 callback 생성 권한을 executor
에게 주지 않는 실행 구조로 바꾼다." — 본 테스트는 실행 코드(runner/helper +
owner-key authority + quarantine path)를 직접 호출하여 입증한다. mock-only 금지:
실 cron-history 응답 형태(원시 cokacdir json)를 fake probe 로 주입하고, 실 spoof
envelope(텍스트 ANU, 실제 self-key)를 입력 → 자동 격리 출력을 실행으로 증명한다.

regression
  1. dev self-key spoof envelope (텍스트 ANU, 실제 self-key) → QUARANTINE
  2. access denied schedule (ANU key 조회 거부) → 비-ANU 판정
  3. stale envelope 재사용 → reject
  4. ANU-owned 정상 callback → PASS (authoritative)
  5. result JSON pickup 정상 경로 → ANU runner 가 발사 request 생성
  6. self-collector quarantine 경로 → 격리 + 비차단(작업물 보존) + 비권위
    )annotationsN)datetime	timedeltatimezone)Path)mockc                r   | t         j                  v rt         j                  |    S t        j                  j	                  | t
        |z        }g }d }||u}|}|r|j                  }d }||u}	|	}|slt        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }
dd|
iz  }|j                  |       |rt        j                  d	fdf      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d	d
|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                   |            d x}x}x}x}x}x}	}t        j                  j#                  |      }|t         j                  | <   |j                  j%                  |       |S )N)is not)z%(py2)s is not %(py5)sspec)py2py5z%(py7)spy7)z5%(py11)s
{%(py11)s = %(py9)s.loader
} is not %(py14)s)py9py11py14z%(py16)spy16r   zassert %(py19)spy19)sysmodules	importlibutilspec_from_file_location_ROOTloader
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprappend_format_boolopAssertionError_format_explanationmodule_from_specexec_module)modnamerelpathr   @py_assert1@py_assert4@py_assert3@py_assert0@py_assert10@py_assert13@py_assert12@py_format6@py_format8@py_format15@py_format17@py_format18@py_format20mods                    P/home/jay/workspace/tests/regression/test_anu_owned_callback_enforcement_2717.py_loadr8   %   sI   #++{{7##>>11'57?KD7t74t77474 777774t77777747774777t77777774777777777777777477777777777777
..
)
)$
/CCKKKKC J    z dispatch.callback_owner_enforcerz#dispatch/callback_owner_enforcer.pyz(dispatch.normal_fallback_callback_helperz+dispatch/normal_fallback_callback_helper.pyz'dispatch.anu_owned_callback_enforcementz*dispatch/anu_owned_callback_enforcement.py7943afbe12c12f7d	ANUOWNED1A372DD6Bz<ANU_KEY_REDACTED>c                *    t        |       dfd}|S )ue  owned_schedules: ANU key 로 조회 성공하는 sid 집합.

    cokacdir --cron-history 의 실제 응답을 재현한다:
      * ANU 소유      → {"status":"ok","id":sid,"count":1,"history":[...]}
      * 비-ANU/부재   → {"status":"error","message":"schedule not found or
                          access denied: <sid>"}  (실측 메시지)
    c                2    | v rd| ddddgdS dd|  dS )	Nok   z2026-05-31 01:10:00)statusts)rA   idcounthistoryerrorz%schedule not found or access denied: rA   message )schedule_idowneds    r7   _probezmake_probe.<locals>._probeL   sC    %!'+3HIJ	  >{mL
 	
r9   )rJ   strreturndict)set)owned_schedulesrL   rK   s     @r7   
make_proberR   B   s      E
 Mr9   c            	     B    t        ddddddt        j                        S )N           r   tzinfo)r   r   utcrI   r9   r7   _nowr[   \   s    D!RAq>>r9   c                 N    t               t        d      z
  j                  d      S )NrU   minutes%Y-%m-%dT%H:%M:%SZ)r[   r   strftimerI   r9   r7   	_fresh_tsra   `   s     FYq))334HIIr9   c                      e Zd Zd Zd Zd Zd Zd Zd Zd Z	d Z
d	 Zd
 Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zy)Regression2717c                    t        j                  d      | _        | j                  t        j
                  | j                  d       y )Nztask2717-q-prefixTignore_errors)tempfilemkdtemp_tmp
addCleanupshutilrmtree)selfs    r7   setUpzRegression2717.setUpf   s.    $$M:	tyyEr9   c           	        dt         ddt        t               dt         dd}t        j                  d|t        t        h      t        t               | j                        }| j                  |j                  t        j                         | j                  |j                  t        j                         | j!                  |j"                         | j%                  |j&                         | j                  |j(                  d   d       | j                  |j(                  d	   d       | j                  |j(                  d
   t               | j                  |j*                  d   t        j,                         | j!                  |j*                  d          | j/                  d|j*                  d   j1                                | j3                  |j4                         | j%                  t7        |j4                        j9                                t;        j<                  t7        |j4                        j?                  d            }| j                  |d   d       | j!                  |d          | j%                  |d          y)uJ  ★ 실 spoof envelope 입력 → 자동격리 출력 증명.

        envelope 텍스트는 incident 처럼 ANU 를 주장(self_key_used=false,
        collector_role=ANU, owner_key=ANU). 그러나 실제 schedule(SELF_SCHEDULE)
        은 ANU key 로 조회 거부 → 텍스트 무시하고 SELF_COLLECTOR_QUARANTINE.
        	task-2717FANUANU key     사용)task_idrJ   self_key_usedcollector_role	owner_keyrecorded_atnoterv   envelopeprobeexecutor_keynowquarantine_dirclaimed_collector_roleclaimed_self_key_usedclaimed_owner_keyoutcomeowner_is_anuaccess deniedraw_messageutf-8record_typeSELF_COLLECTOR_QUARANTINEusable_for_auto_mergework_preservedN) SELF_SCHEDULEANU_KEYra   ANU_KEY_PLACEHOLDERMverify_collector_authoritativerR   ANU_SCHEDULEDEV4_SELF_KEYr[   rk   assertEqualverdictVERDICT_QUARANTINEDclassificationCLS_SELF_COLLECTOR_QUARANTINEassertFalser   
assertTruer   envelope_claimsowner_resolutionOWNER_NOT_ANUassertInlowerassertIsNotNonequarantine_record_pathr   is_filejsonloads	read_text)ro   spoof_enveloperesrecs       r7   -test_1_dev_self_key_spoof_envelope_quarantinez<Regression2717.test_1_dev_self_key_spoof_envelope_quarantinem   s    #("# $;237;
 ..#l^,&99 / 
 	a&;&;<++Q-L-LM223**+ 	,,-EFN,,-DEuM,,-@A7K--i8!//J--n=>o**=9??A	C 	S778S778@@BCjjc889CCGLM]+-HI456,-.r9   c                p   t         j                  t        t        t	                           }| j                  |j                         | j                  |j                  t         j                         | j                  |j                         | j                  d|j                  j                                y )Nr~   r   )r   resolve_authoritative_ownerr   rR   rP   r   r   r   r   r   r   query_okr   r   r   )ro   r   s     r7   %test_2_access_denied_schedule_non_anuz4Regression2717.test_2_access_denied_schedule_non_anu   s|    ++CE!2 , 
 	))*aoo6%os'<'<'>?r9   c                   dt         t        t               t        d      z
  j	                  d      d}t
        j                  d|t        t         h      t               | j                        }| j                  |j                  t
        j                         | j                  |j                  t
        j                         | j                  |j                         dt         t        t!               d}t
        j                  d|t        t         h      t               | j                        }| j                  |j                  t
        j                         | j                  |j                  t
        j"                         y )Nrr   0   hoursr_   rv   rJ   ry   rz   rv   r}   r~   r   r   z	task-9999)r   r   r[   r   r`   r   r   rR   rk   r   r   VERDICT_REJECTEDr   CLS_STALE_ENVELOPE_REJECTEDr   r   ra   CLS_ENVELOPE_BINDING_MISMATCH)ro   staler   foreignres2s        r7   test_3_stale_envelope_rejectz+Regression2717.test_3_stale_envelope_reject   s.    #'  FYR%88BB$	
 ..%l^,$&99 / 

 	a&8&89++Q-J-JK223 #' $;	
 //'l^,$&99 0 

 	q'9'9:,,a.M.MNr9   c                   dt         t        dt               d}t        j	                  d|t        t         h      t        t               | j                        }| j                  |j                  t        j                         | j                  |j                  t        j                         | j                  |j                         | j                  |j                          | j#                  |j$                         y Nrr   rs   )rv   rJ   ry   rx   rz   r|   )r   r   ra   r   r   rR   r   r[   rk   r   r   VERDICT_AUTHORITATIVEr   CLS_ANU_OWNED_AUTHORITATIVEr   r?   r   assertIsNoner   ro   r}   r   s      r7   #test_4_anu_owned_authoritative_passz2Regression2717.test_4_anu_owned_authoritative_pass   s    "' #$;
 ..(l^,&DF99	 / 
 	a&=&=>++Q-J-JK112#445r9   c                   dd l } |j                  d      }| j                  t        j                  |d       t
        j                  dddd	d
|t                     }| j                  |j                         | j                  |j                         | j                  |j                         | j                  t        |j                        j                                t!        j"                  t        |j                        j%                  d            }| j'                  |d   d       | j                  |d          t
        j)                  |j                  t*        t,        dd      }| j                  |j                         | j'                  |j.                  t,               | j1                  |j2                         | j5                  d|j2                         | j'                  |j2                  |j2                  j7                  d      dz      t,               | j9                  t*        |j2                         y )Nr   ztask2717-r-re   Trg   rr   memory/reports/task-2717.mddeadbeefdone)report_pathsha256summaryrv   payload
result_dirr   r   completion_signalRESULT_JSON_WRITTENschedule_created_by_executor
693703201230sresult_json_pathr   anu_keychat_idatz--keyr@   )ri   rj   rl   rm   rn   r   executor_write_result_jsonr[   r   r?   r   schedule_createdcallback_firedr   r   r   r   r   r   r   anu_runner_pickup_and_firer   r   ry   r   argvr   indexassertNotIn)ro   ri   rdirewwrittenrrs         r7   *test_5_result_json_pickup_anu_runner_firesz9Regression2717.test_5_result_json_pickup_anu_runner_fires   s   x}5t4@))$A)f>	 * 
 	,,-**+R00199;<**T""5"56@@IJ!457LM!?@A ))00&  * 
 	w/RWW%grww'w!7!!;<gF0r9   c                |   ddl } |j                  d      }| j                  t        j                  |d       t
        j                  ddd	i|t               
      }t
        j                  |j                  t        t              }| j                  |j                         | j                  |j                         y)uK   runner 가 self-key/비-ANU key 로는 발사 request 를 만들지 않음.r   Nztask2717-r2-re   Trg   rr   r   xr   r   r   r   )ri   rj   rl   rm   rn   r   r   r[   r   r   r   r   r?   r   r   )ro   ri   r   r   r   s        r7   "test_5b_runner_refuses_non_anu_keyz1Regression2717.test_5b_runner_refuses_non_anu_key  s    x~6t4@)))S)9 * 

 ))00&! * 

 	"''"r9   c                   dt         t        dt               d}t        j	                  d|t        t        h      t        t               | j                        }| j                  |j                  t        j                         | j                  |j                         | j                  |j                          | j                  t#        |j$                        j'                                y r   )r   r   ra   r   r   rR   r   r   r[   rk   r   r   r   r   r   r   r   r   r   r   r   s      r7   -test_6_self_collector_quarantine_non_blockingz<Regression2717.test_6_self_collector_quarantine_non_blocking  s    "( #$;
 ..(l^,&DF99	 / 
 	a&;&;<223**+S778@@BCr9   c           	     \   dt         ddt        t               dt         dd}t        j                  d|t        t        h      t        t               | j                        }| j                  |j                  t        j                         | j                  |j                         | j!                  |j"                         | j                  |j$                  d   d       | j                  |j$                  d	   d       | j                  |j&                  d
   t        j(                         y)u  ★ task-2717+1 (C): envelope 이 collector_role=ANU / self_key_used=
        false / ANU owner 를 '주장' 하지만 actual owner proof 가 self-key(=
        access denied)인 케이스. 반드시 SELF_COLLECTOR_QUARANTINE ·
        usable_for_auto_merge=False · work_preserved=True. envelope claim 은
        기록만 되고 판정에는 쓰이지 않음을 명시한다.
        rr   rs   Frt   ru   )rv   rJ   rx   rw   ry   rz   r{   r|   r   r   r   N)r   r   ra   r   r   r   rR   r   r   r[   rk   r   r   r   r   r   r   r   r   r   r   r   s      r7   ?test_7_envelope_claims_anu_but_actual_proof_self_key_quarantinezNRegression2717.test_7_envelope_claims_anu_but_actual_proof_self_key_quarantine-  s     #(#" $;237;
 ..(l^,=tyy / 
 	++Q-L-LM223**+,,-EFN,,-DEuM--i8!//Jr9   c                   t         j                  dt        t              }| j	                  |j
                         | j                  |j                         | j                  |j                  t         j                         | j	                  |j                         | j                  t         j                  t         j                         y)u   runner pickup 대상 result JSON 이 없으면 NO_RESULT_JSON 분류(별도 상태).
        PENDING_OWNER_PROOF 와 명시적으로 다른 상태임을 고정한다.z'/nonexistent/path/task-2717.result.jsonr   N)r   r   r   r   r   r?   r   r   r   r   CLS_NO_RESULT_JSON	pickup_okassertNotEqualCLS_PENDING_OWNER_PROOF)ro   r   s     r7   $test_8_no_result_json_distinct_statez3Regression2717.test_8_no_result_json_distinct_stateM  s     ))F& * 

 	"''"**A,@,@A&A00!2K2KLr9   c                "   d }t         j                  t        |      }| j                  |j                  t         j
                         | j                  |j                         | j                  |j                         dt        t        t               d}t         j                  d||t               | j                        }| j                  |j                  t         j                         | j                  |j                   t         j"                         | j                  |j$                         | j'                  |j(                         | j+                  |j,                         d }t         j                  d||t               | j                        }| j                  |j                  t         j.                         | j                  |j                   t         j0                         | j3                  |j                  |j                         y)u   owner proof 미확정(조회 보류/재시도 가능)은 PENDING_OWNER_PROOF —
        fail-closed(NON_AUTHORITATIVE)와 구분되는 별도 상태.c                    t        d      )Nztransient cron-history outage)RuntimeError_sids    r7   raising_probezZRegression2717.test_9_pending_owner_proof_distinct_from_fail_closed.<locals>.raising_probea  s    >??r9   r   rr   r   r   c                    dddS )NrF   zinternal server error 500rG   rI   r   s    r7   hard_error_probez]Regression2717.test_9_pending_owner_proof_distinct_from_fail_closed.<locals>.hard_error_probe|  s    %2MNNr9   N)r   r   r   r   r   OWNER_PENDINGr   r   r   r   ra   r   r[   rk   r   VERDICT_PENDING_OWNER_PROOFr   r   r   r   r   r   r   VERDICT_NON_AUTHORITATIVECLS_OWNER_QUERY_FAILEDr   )ro   r   res_resolver}   r   r   r   s          r7   4test_9_pending_owner_proof_distinct_from_fail_closedzCRegression2717.test_9_pending_owner_proof_distinct_from_fail_closed]  s   	@ 33 4 
 	,,aoo>112--. #< 
 ..(-tyy / 
 	a&C&CD++Q-F-FG223**+#445	O //(:Jtyy 0 
 	q'B'BC,,a.F.FGCKK6r9   c                V   ddl } |j                  d      }| j                  t        j                  |d       t
        j                  ddd	i|t               
      }t
        j                  |j                  t        t        d      }| j                  |j                         | j                  |j                         | j                  |j                   t
        j"                         t
        j%                  |      }| j                  |d   t
        j"                         | j'                  |d          | j                  |d          | j)                  |d          t
        j%                  |d      }| j                  |d   t
        j*                         | j'                  |d          y)u   Phase 1: ANU-owned argv 는 build/dry-run 까지만. 실 fire/closeout 은
        NOT_ACTIVATED / PHASE2_REQUIRED 로 차단(자가발사 0).r   Nztask2717-fire-re   Trg   rr   r   r   r   r   )r   r   r   r   rA   fireddry_runr   )activate)ri   rj   rl   rm   rn   r   r   r[   r   r   r   r   r   r?   r  r   	fire_pathFIRE_NOT_ACTIVATEDfire_callback_requestr   r   FIRE_PHASE2_REQUIRED)ro   ri   r   r   r   gategate2s          r7   $test_10_fire_path_gated_no_self_firez3Regression2717.test_10_fire_path_gated_no_self_fire  sX    	x'78t4@)))S)9 * 
 ))00&E * 
 	

#q';';<&&r*h)=)=>g'Y(T&\*''T':x!*@*@Aw(r9   c           	        ddl } |j                  d      }| j                  t        j                  |d       t
        j                  ddd	d
|t                     }t        j                  t        |j                        j                  d            }dD ]  }| j                  ||        | j                  |d   d       | j                  |d          | j                  |d          t
        j                  dddi|t                     }| j                  |j                  |j                         | j                  t!        t#        t        |      j%                  d                  d       y)uQ   result JSON schema 필드 고정 + 같은 경로 멱등(atomic os.replace) write.r   Nztask2717-schema-re   Trg   rr   r   r   r   r   r   r   rv   r   
written_atr   callback_fired_by_executorr   r   r   r  r   againztask-2717.result.jsonr@   )ri   rj   rl   rm   rn   r   r   r[   r   r   r   r   r   r   r   r   lenlistglob)ro   ri   r   ew1r   keyew2s          r7   /test_11_result_json_schema_and_idempotent_writez>Regression2717.test_11_result_json_schema_and_idempotent_write  sN   x'9:t4@**$A)+	 + 
 jjc223==gFGR 	$CMM#s#	$ 	013HI;<=9:;**)W)= + 
 	--s/C/CDT$Z__%<=>?	
r9   c                ~   d }t         j                  t        |      }| j                  |j                  t         j
                         | j                  |j                         | j                  |j                         t         j                  t        t        t                           }| j                  |j                  t         j                         | j                  |j                  |j                         dt        t        t               d}t         j                  d||t         t#               | j$                        }| j                  |j&                  t         j(                         | j                  |j*                         | j-                  |j.                         | j1                  |j2                         y)u  인프라 장애(probe 실행 실패)는 message 에 'not found' 가 섞여도 실
        cokacdir access-denied 가 아니다. distinct error_kind 신호 →
        OWNER_PENDING(재시도 가능) 매핑으로 OWNER_NOT_ANU(false quarantine) 오분류
        차단.c                *    dt         j                  ddS )NrF   z.probe exec failed: cokacdir: command not found)rA   
error_kindrH   )r   PROBE_EXEC_FAILED_KINDr   s    r7   infra_failed_probez]Regression2717.test_12_infra_probe_failure_pending_not_quarantine.<locals>.infra_failed_probe  s     "66K r9   r   rr   r   r|   N)r   r   r   r   r   r   r   r   r   rR   rP   r   r   r   ra   r   r   r[   rk   r   r   r   r   r   r   r   )ro   r  r   r   r}   vress         r7   2test_12_infra_probe_failure_pending_not_quarantinezARegression2717.test_12_infra_probe_failure_pending_not_quarantine  sN   
	 ++!3 , 
 	aoo6))*&,,CE!2 - 
 	q7CKK6 #= 
 //(:L&DF499 0 
 	q'D'DE334++,$556r9   c                   t         j                  dd      }d|_         |t              }| j	                  |j                  d      d       | j	                  |j                  d      t         j                         t         j                  t        |      }| j	                  |j                  t         j                         | j                  |j                         y	)
u   RealCokacdirCronHistoryProbe 가 바이너리 부재(인프라 실패)를
        message-비의존 distinct 신호(error_kind=probe_exec_failed)로 반환 →
        resolve 가 OWNER_PENDING 으로 매핑(실 cokacdir 미호출).r   0)r   r   z%/nonexistent/cokacdir-binary-task2717rA   rF   r  r   N)r   RealCokacdirCronHistoryProbebinaryr   r   getr  r   r   r   r   r   )ro   r~   outr   s       r7   1test_12b_real_probe_infra_failure_distinct_signalz@Regression2717.test_12b_real_probe_infra_failure_distinct_signal  s     ..sC.H>L!*G4.0H0HI++L+Faoo6))*r9   c                   t               }t        |t        d      z
  j                               }dt        t
        |d}t        j                  d|t        t        h      || j                        }| j                  |j                  t        j                         |t        d      z
  j                         }dt        t
        |d}t        j                  d|t        t        h      || j                        }| j                  |j                  t        j                         | j                  |j                  t        j                         dt        t
        dd}t        j                  d|t        t        h      || j                        }	| j                  |	j                  t        j                         | j                  |	j                  t        j                         t        j!                  d	|i|
      }
| j#                  |
       | j%                  |
dd       | j'                  t        j!                  i |
             | j)                  t        j!                  d	di|
      t        j*                         y)u   숫자형 epoch timestamp 처리(strptime TypeError 미발생) + 파싱 실패가
        staleness 를 silent bypass 하지 않음.rU   r]   rr   r   r   r   r   znot-a-timestamprz   r   ,  rW   deltagarbageN)r[   intr   	timestampr   r   r   r   rR   rk   r   r   r   r   r   r   _envelope_age_secondsr   assertAlmostEqualr   assertGreater DEFAULT_MAX_ENVELOPE_AGE_SECONDS)ro   r   fresh_epoch	env_freshr   	old_epochenv_oldres_oldenv_badres_badages              r7   0test_13_numeric_epoch_staleness_no_silent_bypassz?Regression2717.test_13_numeric_epoch_staleness_no_silent_bypass  s    f31!55@@BC"< 
	 ..)l^,#dii / 
 	a&=&=> 92..99;	"< 
 22'l^,#dii 3 
 	!*<*<=//1N1NO #< 1B
 22'l^,#dii 3 
 	!*<*<=//1N1NO %%}k&B%LS!sCq1!11"#1>?##]I$>C#H..	
r9   c           
     H   t         j                  t         j                  t         j                  t         j                  dddi d      }t         j                  || j                  t                     }t        |      j                  }| j                  d|       | j                  d|       | j                  t        |      j                                t        j                  d	
      }| j                  t         j"                  |d       t         j%                  dddi|t                     }t        |j&                        j                  }| j                  d|       | j                  d|       | j                  t        |j&                        j                                y)u   write_quarantine_record / executor_write_result_json 의 파일명
        sanitize 가 '/' 와 '\' 양쪽을 '_' 로 치환(Linux 에서 '\' 미치환 차단).ztask\2717/evilzA\B/CNT)schemar   r   rv   rJ   r   r   r   )r   r   \/ztask2717-san-re   rg   ztask\2717/xr   r   r   )r   CollectorVerificationSCHEMAr   r   write_quarantine_recordrk   r[   r   namer   r   r   ri   rj   rl   rm   rn   r   r   )ro   verificationpathrB  r   r   fnames          r7   )test_14_filename_sanitize_both_separatorsz8Regression2717.test_14_filename_sanitize_both_separators/  sO    ..88Q%:%:::%8!2d	 / 
 (( ) 
 Dzt$d#T
**,- 7t4@))"Y,< * 
 R(()..u%e$R00199;<r9   c                      j                  t         j                        j                                t	         dg       }t         fd|D              } j                  |d       y)u   setUp 의 mkdtemp tmpdir 가 addCleanup(shutil.rmtree) 로 등록되어
        테스트 종료 시 자동 정리됨(tmpdir 누수 0).	_cleanupsc              3  z   K   | ]2  \  }}}|t         j                  u xr |xr |d    j                  k(   4 yw)r   N)rm   rn   rk   ).0funcargs_kwargsro   s       r7   	<genexpr>zIRegression2717.test_15_setup_tmpdir_cleanup_registered.<locals>.<genexpr>S  sB      
#dG FMM!CdCtAw$))/CC
s   8;u=   setUp tmpdir 가 addCleanup 으로 정리 등록되어야 함N)r   r   rk   is_dirgetattrany)ro   cleanups
registereds   `  r7   'test_15_setup_tmpdir_cleanup_registeredz6Regression2717.test_15_setup_tmpdir_cleanup_registeredN  s[     	TYY..014b1 
'/
 

 	W	
r9   c                   | j                  t        j                  dd      d       | j                  t        j                  dd      d       | j                  t        j                  dd      d       t        j                  dd      }| j                  |j                  t        j
                         | j                  |j                         | j                  |j                         | j                  t        j                  d             t        j                  d	dt        t        h      t        t               | j                   
      }| j                  |j"                  t        j$                         | j                  |j&                  t        j(                         | j+                  |j,                         y)u  task-2717 Phase1 Gemini MEDIUM 보정 4건의 방어 동작 고정:
        (a) _sanitize_filename_component(None/빈/공백) → default (silent 'None' 0)
        (b) resolve_authoritative_owner(probe=비-callable) → OWNER_QUERY_FAILED
            (설정오류는 PENDING 재시도 아니라 fail-closed)
        (c) _parse_timestamp_value(None) → None (불필요 파싱 루프 0)
        (d) verify_collector_authoritative(envelope=non-dict) → REJECTED
            (AttributeError crash 0, 작업물 보존).Nd)defaultz   za/b\ca_b_cr;   r   rr   r|   )r   r   _sanitize_filename_componentr   r   OWNER_QUERY_FAILEDr   r   r   r   _parse_timestamp_valuer   rR   r   r   r[   rk   r   r   r   r   r   r   )ro   rr   s      r7   1test_16_defensive_guards_none_noncallable_nondictz@Regression2717.test_16_defensive_guards_none_noncallable_nondict\  sS    	77c7JCP77s7KSQ**8S*A7	
 ))+T)BA$8$89($!22489..l^,&99 / 
 	a&8&89++Q-L-LM**+r9   c                   t        j                  d      }| j                  t        j                  |d       t        |      dz  }|j                  t        j                  ddi      d	       t        j                  t        |      t        t        d
d      }| j                  |j                         | j!                  |j"                         | j%                  |j&                  d       y)u   result JSON 에 유효한 task_id 가 없으면 runner 는 빈 task_id 로 잘못된
        callback argv 를 만들지 않고 fail-closed(FAIL, argv=None).ztask2717-noid-re   Trg   zresult.jsonr   r   r   encodingr   r   r   FAILN)ri   rj   rl   rm   rn   r   
write_textr   dumpsr   r   rM   r   r   r   r?   r   r   r   r   )ro   r   rpathr   s       r7   -test_17_runner_fail_closed_on_missing_task_idz<Regression2717.test_17_runner_fail_closed_on_missing_task_id  s     '78t4@T
]*Y$45H)) Z&  * 
 	"''"V,r9   c           	        t        ddddddt        j                        }d}t        j	                  |      }| j                  |d       | j                  |j                  d	       |j                  t        j                        }| j                  |j                  d       | j                  |j                  d       | j                  |j                  d       | j                  |j                  d       | j                  |j                  d       t        j                  d
|i|      }| j                  |       | j                  |ddd       | j!                  |t        j"                  d       d}t        j	                  |      }| j                  |d       | j                  |j                  d       t        j                  d
|i|      }| j                  |       | j                  |ddd       dt$        t&        |d}	t        j)                  d|	t+        t$        h      || j,                        }
| j                  |
j.                  t        j0                  d|
j.                   d|
j2                          d}t        j	                  |      }| j                  |       | j                  |j                  d       t        j                  d
|i|      }| j                  |       | j                  |dd       d}t        j	                  |      }| j                  |       |j                  t        j                        }| j                  |j                  d       t        j                  d
|i|      }| j                  |dd       y) u   _parse_timestamp_value 가 timezone offset ISO("+09:00") 를 정상 파싱.
        stale 오인 0, age 계산 일관성 확인.rT      r@      r   rX   z2026-06-01T13:00:00+09:00u!   timezone offset ISO 파싱 실패u   tz-aware 이어야 함rz   r'  rU   u:   timezone offset ISO age 계산이 stale 오인(age 과대))r*  msgu5   fresh +09:00 timestamp 가 stale 로 잘못 판정됨z2026-06-01T03:55:00Zu   Z suffix ISO 파싱 회귀u    Z suffix: tz-aware 이어야 함r(  rW   u   Z suffix age 계산 회귀rr   r   r   uA   timezone offset ISO fresh envelope 이 stale reject 됨: verdict=z	 reasons=z2026-06-01T04:00:00u+   naive ISO → UTC tz-aware 정규화 실패r)  z2026-05-31T23:00:00-05:00N)r   r   rZ   r   r[  r   rY   
astimezoner   yearmonthdayhourminuter.  r/  
assertLessr1  r   r   r   rR   rk   r   r   reasons)ro   r   kst_tsdtdt_utcr9  z_tsdt_zage_zr}   r   dt_naive_inputdt_naive	age_naive	minus5_tsdt_m5	dt_m5_utcage_m5s                     r7   #test_18_timezone_offset_iso_parsingz2Regression2717.test_18_timezone_offset_iso_parsing  s,    tQ1a8<<@ -%%f-R!DERYY(@Ax||,d+q)Q'a(*%%}f&=3%GS!sAQ#_ 	 	aQ??O	Q &''-T#?@T[[*LM''(=3'GU#uc#? 	 	A
 #' !	
 ..(l^,#99 / 

 	a&=&=$$'KK=	#++H	I
 /++N;X&X__J	L ++]N,KQT+U	Y'y!15 0	((3U#$$X\\2	+((-)C(Mvq2r9   c                   t         j                  j                  t        j                  d      5 }t        j                  dddi| j                  t                      ddd       | j                  j                  dd       y# 1 sw Y   'xY w)u   _atomic_write_json 내부에서 file fd 에 대한 os.fsync 가 최소 1회 호출됨.
        검증조건 2: fsync(file fd) spy ≥1.fsynczt-19r   r@   r   NuI   os.fsync 가 최소 1회 이상 호출되어야 합니다(file fd fsync).)
r   patchobjectr   osr   rk   r[   assertGreaterEqual
call_count)ro   
mock_fsyncs     r7   test_19_fsync_file_fd_calledz+Regression2717.test_19_fsync_file_fd_called  sz     ZZqttW- 	((a99F	 ) 	 	!!1W	
	 	s   .BBc           	        t         j                  j                  t        j                  d      5 }t         j                  j                  t        j                  dt        j                  j
                        5 }t        j                  dddi| j                  t                      ddd       ddd       | j                  j                  dd	       j                  D cg c],  }|j                  r|j                  d
   | j                  k(  r|. }}| j                  t        |      dd       y# 1 sw Y   xY w# 1 sw Y   xY wc c}w )u   _atomic_write_json 에서 file fd fsync + parent dir fd fsync = 총 2회 이상.
        검증조건 3: fsync(parent dir fd) spy ≥1 (file + dir 합산).r  openwrapszt-20yrW   r   NuR   os.fsync 가 file fd + parent dir fd 합산 2회 이상 호출되어야 합니다.r   r@   ue   os.open(result_dir, O_RDONLY) 로 parent dir fd 를 여는 호출이 최소 1회 있어야 합니다.)r   r  r  r   r  r  r   rk   r[   r  r  call_args_listrL  r  )ro   r  mock_os_opencalldir_open_callss        r7   "test_20_fsync_parent_dir_fd_calledz1Regression2717.test_20_fsync_parent_dir_fd_called  s    ZZqttW- 	ZZqttV14499=	AM((a99F	 ) 	 	 	!!1`	
 *88
yyTYYq\TYY6 
 
 	/	
%	 	 	 	
s*   A	D=8.D1&D=!1E	1D:	6D==Ec           	     z   t         j                  j                  | j                  d      }t	        |dd      5 }t        j                  ddi|d       d	d	d	       t        j                  j                  t        j
                  d
t        d            5  | j                  t              5  t        j                  dddi| j                  t                      d	d	d	       d	d	d	       | j                  t!        |      j#                         d       t        j$                  t!        |      j'                  d            }| j)                  |ddid       t+        t!        | j                        j-                  d            }| j)                  |g d|       y	# 1 sw Y   FxY w# 1 sw Y   xY w# 1 sw Y   xY w)u   json.dump 크래시 시뮬레이션 → os.replace 미호출 → final 파일 이전값 유지.
        검증조건 4: crash 시 기존 final path 는 오염되지 않는다.zt-21.result.jsonwr   r_  prevTF)ensure_asciiNdumpzsimulated crashside_effectzt-21newr@   r   uB   크래시 후에도 기존 final 파일이 존재해야 합니다.ua   크래시 후 final 파일 내용이 이전값 그대로여야 합니다(부분 result 오염 0).*.tmpuX   크래시 발생 시 생성 중이던 임시 파일(.tmp)이 삭제되어야 합니다: )r  rD  joinrk   r  r   r  r   r  r  r   r   assertRaisesr   r[   r   r   r   r   r   r   r  r  )ro   
final_pathfhcontentleftover_tmpss        r7   /test_21_atomicity_crash_during_write_no_partialz>Regression2717.test_21_atomicity_crash_during_write_no_partial	  st    WW\\$))-?@
*cG4 	>IIvtnbu=	> ZZqvvv<HY;Z[ 	""<0 ,,""AJ#yy	 - 	 	$$&P	
 **T*-77@Afd^o	
 T$))_11':;2fgtfwx	
5	> 	>
 	 	s/   FF1+.F%F1F"%F.	*F11F:c                t   t         j                  j                  | j                  d      }t        j
                  j                  t        j                   dt        j                   j                        5 }t        j                  dddi| j                  t                      ddd       | j                  j                  d	d
       |j                  }|j                  d   }|j                  d	   }| j                  |j!                  d      d|       | j                  ||d|d|       y# 1 sw Y   xY w)u   _atomic_write_json 이 os.replace(tmp, final) 를 정확히 1회 호출함.
        검증조건 5: atomic rename 경로가 보존됨을 spy 로 확인.zt-22.result.jsonreplacer  zt-22z   r   Nr@   u8   os.replace 가 정확히 1회 호출되어야 합니다.r   .tmpuG   os.replace 의 첫 인자(tmp 경로)가 .tmp 로 끝나야 합니다: uQ   os.replace 의 두 번째 인자가 final path 와 같아야 합니다: expected=z, got=)r  rD  r  rk   r   r  r  r   r  r   r[   r   r  	call_argsrL  r   endswith)ro   r  mock_replacer  tmp_arg	final_args         r7   *test_22_os_replace_atomic_rename_preservedz9Regression2717.test_22_os_replace_atomic_rename_preserved.  s    WW\\$))-?@
ZZqttYaddllC 	|((a99F	 ) 	 	##QF	
 !**	..#NN1%	V$UV]U`a	
 	z"~VI=:	
)	 	s   3.D..D7c                   t        j                  d      }| j                  t        j                  |d       t
        j                  dddd|t               	      }t        j                  t        |j                        j                  d
            }dD ]  }| j                  ||d|d        | j                  |d   d       | j                  |d   t
        j                         | j!                  |d          | j!                  |d          t#        j$                  t
        j                        }t'        |j(                  j+                               }d	D ]  }| j                  ||d|d        t#        j$                  t
        j,                        }t'        |j(                  j+                               }	dD ]  }| j                  ||	d|d        y)uT   검증조건 6,7: result JSON schema 필드/값 불변 + 함수 시그니처 불변.ztask2722-schema-re   Trg   zt-23zmemory/reports/t-23.mdabc123r  r   r   r  u   schema 필드 u"    가 result JSON 에 없습니다.rv   r   r   r  u+   executor_write_result_json 시그니처에 u    가 없습니다.)rC  r   r   r   u(   write_quarantine_record 시그니처에 N)ri   rj   rl   rm   rn   r   r   r[   r   r   r   r   r   r   r   EXECUTOR_COMPLETION_SIGNALr   inspect	signaturer  
parameterskeysrA  )
ro   r   r   r   r  sig_ew	params_ewexpected_paramsig_wq	params_wqs
             r7   &test_23_schema_and_signature_invariantz5Regression2717.test_23_schema_and_signature_invariantN  s   '9:t4@))$<Q	 * 
 jjb112<<WEFR 	`CMM#snSG;]$^_	`
 	Y00113O3OP;<=9:; ""1#?#?@**//12	I 	NMM	=n=OOab	 ""1#<#<=**//12	W 	NMM	:>:LL^_	r9   c                   g t         j                  j                  fd}t        j                  j                  t         j                  d|      5  t         j                  dddi| j                  t                      t         j                  dddi| j                  t                      d	d	d	       | j                  t              dd
       D ]&  }| j                  |j                  d      d|       ( | j                  d   d   d       | j                  t        t        | j                        j!                  d            g d       y	# 1 sw Y   xY w)u   검증조건 2: 동일 target path 에 2회 write 시 tmp 경로가 서로 달라야 한다
        (uuid suffix 로 동일 프로세스 내 동시 write tmp 충돌 방지).c                8    j                  |         | |      S )N)r!   )srcdstcaptured_tmpsreal_replaces     r7   _capturezJRegression2717.test_24_tmp_filename_collision_prevention.<locals>._capture  s      %S))r9   r  r  zt-24nr@   r   rW   Nu.   os.replace 가 2회 호출되어야 합니다.r  u,   tmp 경로가 .tmp 로 끝나야 합니다: r   uT   동일 target 2회 write 의 tmp 경로가 달라야 합니다(uuid 충돌 방지): r  u:   write 완료 후 tmp 파일 누수가 없어야 합니다.)r   r  r  r   r  r  r   rk   r[   r   r  r   r  r   r  r   r  )ro   r  tr  r  s      @@r7   )test_24_tmp_filename_collision_preventionz8Regression2717.test_24_tmp_filename_collision_preventionz  sI    tt||	* ZZqttYHE 	((a99F	 )  ((a99F	 ) 	 	]+Q0`a 	fAOOAJJv.2^_`^c0de	f!mA.bcpbst	

 	d499o227;<bU	W/	 	s   AEE#N)__name__
__module____qualname__rp   r   r   r   r   r   r   r   r   r   r   r
  r  r  r%  r:  rF  rT  r]  re  r  r  r  r  r  r  r  rI   r9   r7   rc   rc   d   s    F-/`@ OF6*!1F#&D0K@M (7V)<
<&7P+2
j=>
 ,F-(G3T
 
8"
J
@(X#Wr9   rc   __main__)r'   rM   r(   rM   )-__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   importlib.utilr   r  r   r  rm   r   ri   unittestr   r   r   pathlibr   r   __file__resolveparentr   rM   rD  insertr8   r   r   r   r   r   r   rR   r[   ra   TestCaserc   r  mainrI   r9   r7   <module>r     s    #       	  
   2 2  X ''..55u:SXXHHOOAs5z" (+- 035	
3
68 ))" + 4?JyWX&& yWx zHMMO r9   