
    wjXI                       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mZ ddlZ ee      j                         j                   d   Z ee      e
j&                  vr"e
j&                  j)                  d ee             ddlmZmZmZmZmZmZmZmZ ddlmZ dZ d	Z!d
e dd&dZ"dddd	 	 	 	 	 d'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 Z0d Z1d Z2d Z3d  Z4d! Z5d" Z6d# Z7d$ Z8d% Z9y)(uu  anu_v2.tests.test_owner_trigger_race_fix_2554plus1 — HIGH race condition fix 회귀 (task-2554+1).

회장 §명시 (2026-05-12 KST) 필수 fix §1~§6 + medium #3/#4/#6:
  §1 dedupe check 와 API call 사이의 TOCTOU 제거
  §2 PR/head 단위 중복 trigger 가 동시 실행에서도 불가능
  §3 fcntl.flock sidecar lock atomic 적용
  §4 lock 범위 안에서 check → record → call → update 순서 유지
  §5 API call 성공 후 audit fail 시 fail-closed
  §6 same PR/head 동시 2 proc → comment 1 회 (concurrency 파일에서 검증)

  medium #3: _read_all 스트리밍 개선 — _iter_rows
  medium #4: http_post 예외 시 audit FAILED 기록
  medium #6: _read_all 손상 라인 관용 (json.JSONDecodeError skip)
  medium #2/#5: dead code _resolve_owner_repo 제거 정적 검사

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path   )AUDIT_REL_PATHALLOWED_ACTIONDedupeViolationOwnerTriggerAuditRESULT_DEDUPEDRESULT_FAILEDRESULT_PENDINGRESULT_POSTED)OwnerTriggerOnly(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbg   prheadc               x    dd||dddddddd}| d	z  }|j                  t        j                  |      d
       |S )Nz anu_v2.owner_trigger_decision.v1task-2554+1TFr   "POST_GEMINI_REVIEW_TRIGGER_COMMENT/gemini review)schematask_idr   current_head
queue_headcurrent_head_confirmedgemini_evidence_freshnudge_count_for_pr_headallowed_actioncomment_bodyalloweddecision.jsonutf-8encoding)
write_textjsondumps)tmp_pathr   r   dps        I/home/jay/workspace/anu_v2/tests/test_owner_trigger_race_fix_2554plus1.py_write_decisionr.   0   sS    4 "&!&#$>(	A 	?"ALLAL1H    z$tok-RACE-FIX-MUST-NOT-LEAK-2554plus1)	http_posttokenpostsc               d    g fd}t        |       }t        | |xs |fd|      }||fS )Nc                :    j                  | |||d       dddS )Nmethodpathbodyheaders   i'  )statusid)append)r6   r7   r8   r9   r2   s       r-   _default_http_postz)_build_module.<locals>._default_http_postM   s#    dwWXT**r/   c                      S N )r1   s   r-   <lambda>z_build_module.<locals>.<lambda>U   s    u r/   workspace_rootr0   token_provideraudit)r	   r   )r*   r0   r1   r2   r>   rF   mods     ``   r-   _build_modulerH   C   sK     }+ h'E
11$	C ur/   c                   t        |       }d}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                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}|j                         5 }d}t        ||      }|sd	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                  |      t        j                  |      dz  }t        t        j                  |            dx}}d}t        ||      }|sd	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                  |      t        j                  |      dz  }t        t        j                  |            dx}}ddd       y# 1 sw Y   yxY w)uQ   audit.transaction() context manager 가 정의되어 있다 (race fix 진입점).transactionz3OwnerTriggerAudit.transaction must exist (race fix)z7
>assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}hasattrrF   )py0py1py3py5Ncheck_dedupez5assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}txnrecord)r	   rK   
@pytest_ar_format_assertmsg@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationrJ   )r*   rF   @py_assert2@py_assert4@py_format6rQ   s         r-   0test_transaction_context_manager_exists_on_auditr^   ^   s   h'E'_75-(_(__*_______7___7______5___5___-___(______				 &*+wsN++++++++w+++w++++++s+++s+++N++++++++++$%wsH%%%%%%%%w%%%w%%%%%%s%%%s%%%H%%%%%%%%%%& & &s   GK::Lc           
     d   t        |       }|j                  }|j                  }||k7  }|st        j                  d|fd||f      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t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }t        |      }|j                  }	d}
 |	|
      }|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t        j                  |      t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      dz  }t        t        j                  |            dx}x}x}	x}
}|j                  }| t        z  }	||	k(  }|s#t        j                  d|fd||	f      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t        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            dx}x}}	y)u?   sidecar lock 파일은 audit JSONL 본 파일과 다른 경로.)!=)zK%(py2)s
{%(py2)s = %(py0)s.lock_path
} != %(py6)s
{%(py6)s = %(py4)s.path
}rF   )rL   py2py4py6assert %(py8)spy8Nz.jsonl.lockzassert %(py11)s
{%(py11)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.lock_path
})
}.endswith
}(%(py9)s)
}str)rL   rM   rN   rO   py7py9py11==)z8%(py2)s
{%(py2)s = %(py0)s.path
} == (%(py4)s / %(py5)s)r*   r   )rL   ra   rb   rO   )r	   	lock_pathr7   rS   _call_reprcomparerU   rV   rW   rX   rY   rZ   rf   endswithr   )r*   rF   @py_assert1@py_assert5@py_assert3@py_format7@py_format9r[   r\   @py_assert6@py_assert8@py_assert10@py_format12s                r-   <test_transaction_sidecar_lock_path_separated_from_audit_pathrx   h   s    h'E??(ejj(?j((((?j((((((5(((5(((?((((((e(((e(((j(((((((737((77(7777777737773777777u777u777777777(77777777777777::2N22:22222:222222252225222:222222222222222N222N2222222r/   c                   t        |       }|j                         5 }|j                  ddt        t        t
        dddddd
       d	d	d	       |j                         5 }t        j                  t              5  |j                  dt        
       d	d	d	       d	d	d	       y	# 1 sw Y   \xY w# 1 sw Y   xY w# 1 sw Y   y	xY w)uc   transaction 안에서 record(POSTED) 후 같은 txn 안의 check_dedupe 가 DedupeViolation 차단.r   r   r   /repos/o/r/issues/103/commentsr#   Tdeadbeef
r   r   r   actionresultr!   endpointdecision_pathtoken_presenttoken_hash_prefixNr   )
r	   rJ   rR   _HEAD_Ar   r   pytestraisesr   rP   r*   rF   rQ   s      r-   0test_transaction_record_blocks_posted_under_lockr   p   s    h'E				 


((' 0<!0!%%/	

  
			 3]]?+ 	3'2	33 3!
 
"	3 	33 3/   )B B87B,B8 B),B5	1B88Cc                   t        |       }|j                         5 }|j                  ddt        t        t
        dddddd
       d	d	d	       |j                         5 }t        j                  t              5  |j                  dt        
       d	d	d	       d	d	d	       y	# 1 sw Y   \xY w# 1 sw Y   xY w# 1 sw Y   y	xY w)uT   PENDING sentinel 도 transaction.check_dedupe 가 차단 (fail-closed crash safety).r   r   r   rz   r#   Tr{   r|   Nr   )
r	   rJ   rR   r   r   r   r   r   r   rP   r   s      r-   1test_transaction_record_blocks_pending_under_lockr      s    h'E				 


((( 0<!0!%%/	

 
			 3]]?+ 	3'2	33 3
 
 	3 	33 3r   c                .   t        |       }|j                         5 }|j                  ddt        t        t
        ddddddd	       d
d
d
       |j                         5 }|j                  dt               d
d
d
       y
# 1 sw Y   :xY w# 1 sw Y   y
xY w)uI   FAILED 결과는 dedupe 차단 사유가 아님 — 다음 시도 허용.r   r   r   rz   r#   Tr{   HTTP_POST_FAILr   r   r   r}   r~   r!   r   r   r   r   
error_codeNr   )r	   rJ   rR   r   r   r   rP   r   s      r-   ,test_transaction_failed_does_not_block_retryr      s    h'E				 


((' 0<!0!%%/.	

" 
			 /Cg./ /#
 
"/ /s   *A?B?BBc                J	   t        |       }t        |       \  }}}|j                  |ddt              }|j                  }|t
        k(  }|st        j                  d|fd|t
        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              nddz  }d	d
|iz  }	t        t        j                  |	            dx}}t        |      }
d}|
|k(  }|st        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                  |
      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            dx}
x}}|j                         }t        |      }
d}|
|k(  }|st        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                  |
      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            dx}
x}}|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   }|t
        k(  }
|
st        j                  d|
fd|t
        f      t        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              nddz  }dd|iz  }t        t        j                  |            dx}}
y)u`   trigger_gemini_review 전체 흐름이 transaction lock 안에서 직렬화되며 POSTED 기록.orr   ownerrepocurrent_head_actualrj   z.%(py2)s
{%(py2)s = %(py0)s.status
} == %(py4)sr~   r   rL   ra   rb   assert %(py6)src   N   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr2   rL   rM   rN   rc   rd   re   r   rowsr   PENDINGz%(py1)s == %(py4)srM   rb   z%(py1)s == %(py3)srM   rN   assert %(py5)srO   )r.   rH   trigger_gemini_reviewr   r;   r   rS   rm   rU   rV   rW   rX   rY   rZ   r   	_read_all)r*   r   rG   r2   rF   r~   ro   rq   @py_format5rr   r[   rp   r\   rs   r   @py_assert0@py_format4r]   s                     r-   ;test_http_post_called_inside_lock_and_audit_posted_recordedr      s   #H-M%h/C&&##	 ' F ==)=M))))=M))))))6)))6)))=))))))M)))M)))))))u::?:33uu:??Dt99>933tt978)	)	))))	))))))	)))))))78------------------------r/   c                   t        |       }d }t        | |g       \  }}}t        j                  t        d      5  |j                  |ddt               ddd       |j                         }t        |      }d	}||k(  }	|	st        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                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                   |            dx}x}	}|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   }|t"        k(  }|st        j                  d
|fd|t"        f      t        j                  |      dt        j                         v st        j                  t"              rt        j                  t"              nddz  }dd|iz  }t        t        j                   |            dx}}|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    }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# 1 sw Y   oxY w)$u   medium #4: http_post 예외 시 audit FAILED 기록 (lock 안에서 atomic).

    task-2554+2 §1: http_post 직전 PENDING + 예외 시 FAILED — 2 행.
    c                    t        d      )Nznetwork timeout simulatedRuntimeErrorr5   s       r-   failing_httpzFtest_http_post_exception_records_failed_in_audit.<locals>.failing_http   s    677r/   r0   r2   znetwork timeout)matchr   r   r   Nr   rj   r   r   r   r   rd   re   r   r~   r   r   r   r   rc   r   r   r   r   r   rO   r   r   token_value_loggedF)is)z%(py1)s is %(py4)s)r.   rH   r   r   r   r   r   r   r   rS   rm   rU   rV   rW   rX   rY   rZ   r   )r*   r   r   rG   _rF   r   r[   rp   r\   rr   rs   r   rq   r   r   r]   s                    r-   0test_http_post_exception_records_failed_in_auditr      sq   
 $H-M8 "(l"MMCE	|+<	= 
!!' '	 	" 	

 ??Dt99>933tt978)	)	))))	))))))	)))))))78------------------------7< 4$44 $44444 $4444 444$444444447'(1E1(E1111(E111(111E1111111
 
s   OOc                n  
 t        |       }d
d }t        |       }t        | |
fd|      }t        j                  t
              5  |j                  |ddt               ddd       |j                  j                  d	
      }
|v}|st        j                  d|fd
|f      dt        j                         v st        j                  
      rt        j                  
      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t!        t        j"                  |            d}dD ]  }	|	|v}|st        j                  d|fd|	|f      dt        j                         v st        j                  |	      rt        j                  |	      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t!        t        j"                  |            d} y# 1 sw Y   xY w)uD   http_post 예외 시 audit FAILED 기록에 raw token 흔적 없음..ghp_RACE_FIX_SECRET_TOKEN_MUST_NEVER_LEAK_xyzzc                >    t        d|j                  dd             )Nzinclude header: Authorization )r   getr5   s       r-   r   zKtest_http_post_exception_does_not_leak_token_to_audit.<locals>.failing_http   s!    -gkk/2.N-OPQQr/   c                      S r@   rA   )secrets   r-   rB   zGtest_http_post_exception_does_not_leak_token_to_audit.<locals>.<lambda>   s    v r/   rC   r   r   r   Nr$   r%   not inz%(py0)s not in %(py2)sr   rawrL   ra   assert %(py4)srb   )zBearer ghp_github_pat_sent)r.   r	   r   r   r   r   r   r   r7   	read_textrS   rm   rU   rV   rW   rX   rY   rZ   )r*   r   r   rF   rG   r   ro   @py_format3r   r   r   s             @r-   5test_http_post_exception_does_not_leak_token_to_auditr      ss   #H-M=FR h'E
%	C 
|	$ 
!!' '	 	" 	

 **



0C6662 3t3tt33
 
s   H**H4c                R
   t        |       }ddifd}t        | |g       \  }}}t        j                  t              5  |j                  |ddt               ddd       |j                  |ddt              }|j                  }|t        k(  }|st        j                  d	|fd
|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                   |
            dx}}|j#                         }t%        |      }d}||k(  }|st        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                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                   |            dx}x}}|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   }|t&        k(  }|st        j                  d	|fd|t&        f      t        j                  |      dt        j                         v st        j                  t&              rt        j                  t&              nddz  }dd |iz  }t        t        j                   |            dx}}|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   }|t        k(  }|st        j                  d	|fd|t        f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd |iz  }t        t        j                   |            dx}}y# 1 sw Y   xY w)#uA   FAILED audit 후 다시 trigger 호출 가능 (재시도 허용).callsr   c                L    dxx   dz  cc<   d   dk(  rt        d      ddiS )Nr   r   zfirst try failsr;   r:   r   )r6   r7   r8   r9   states       r-   r0   zItest_dedupe_failed_attempts_allows_retry_via_full_flow.<locals>.http_post
  s3    g!>Q011#r/   r   r   r   r   Nrj   r   r2r   r   r   rc      r   r   r   r   rd   re   r~   r   r   r   r   r   r   r   r   rO   r      )r.   rH   r   r   r   r   r   r;   r   rS   rm   rU   rV   rW   rX   rY   rZ   r   r   r   )r*   r   r0   rG   r   rF   r   ro   rq   r   rr   r   r[   rp   r\   rs   r   r   r]   r   s                      @r-   6test_dedupe_failed_attempts_allows_retry_via_full_flowr     s   #H-MaLE "(irJMCE	|	$ 
!!' '	 	" 	

 
	"	"##	 
# 
B 99%9%%%%9%%%%%%2%%%2%%%9%%%%%%%%%%%%%%%%??Dt99>933tt978)	)	))))	))))))	)))))))78------------------------78)	)	))))	))))))	)))))))78------------------------+
 
s   TT&c                   t        |       }t        |       \  }}}|j                  |ddt              }|j                  |ddt              }|j                  }|t
        k(  }|st        j                  d|fd|t
        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              nddz  }	d	d
|	iz  }
t        t        j                  |
            dx}}|j                  }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }	d	d
|	iz  }
t        t        j                  |
            dx}}t        |      }d}||k(  }|st        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                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}}|j!                         }t        |      }d}||k(  }|st        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                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}}|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   }|t
        k(  }|st        j                  d|fd|t
        f      t        j                  |      dt        j                         v st        j                  t
              rt        j                  t
              nddz  }dd|iz  }t        t        j                  |            dx}}|d    d   }|t        k(  }|st        j                  d|fd|t        f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            dx}}y)!u   동일 (pr, head) 두번째 trigger 호출 → DEDUPED + http_post 1 회만.

    task-2554+2 §1: 1차 PENDING+POSTED, 2차 check_dedupe 가 POSTED 감지 → DEDUPED — 3 행.
    r   r   r   rj   r   r1r   r   r   rc   Nr   r
   r   r   r   r2   r   rd   re   r   r   r   r~   r   r   r   r   r   r   rO   r   )r.   rH   r   r   r;   r   rS   rm   rU   rV   rW   rX   rY   rZ   r
   r   r   )r*   r   rG   r2   rF   r   r   ro   rq   r   rr   r[   rp   r\   rs   r   r   r   r]   s                      r-   ,test_dedupe_blocks_after_posted_in_full_flowr   )  s   
 $H-M%h/C		"	"#3Sg 
# 
B 
	"	"#3Sg 
# 
B 99%9%%%%9%%%%%%2%%%2%%%9%%%%%%%%%%%%%%%%99&9&&&&9&&&&&&2&&&2&&&9&&&&&&&&&&&&&&&&u::?:33uu:??Dt99>933tt978)	)	))))	))))))	)))))))78------------------------78........................r/   c                   ddl }t        |       }|j                         }|j                  }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                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}y)u8   _iter_rows 는 Iterator (generator) — 전체 로딩 0.r   NzXassert %(py6)s
{%(py6)s = %(py0)s(%(py1)s, %(py4)s
{%(py4)s = %(py2)s.GeneratorType
})
}
isinstanceittypes)rL   rM   ra   rb   rc   )r   r	   
_iter_rowsGeneratorTyper   rU   rV   rS   rW   rX   rY   rZ   )r*   r   rF   r   rq   rp   rr   s          r-   %test_iter_rows_is_streaming_generatorr   E  s    h'E				B--.:b-........:...:......b...b......%...%...-..........r/   c                   t        |       }|j                          |j                  ddt        t        ddddddd	d
       t        |j                  dd      5 }|j                  d       ddd       |j                  ddt        t        ddddddd	d
       t        |j                               }t        |      }d}||k(  }|st        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"                  |      t        j"                  |      dz  }dd|iz  }t%        t        j&                  |            dx}x}}|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   }	d}
|	|
k(  }|slt        j                  d|fd|	|
f      t        j"                  |	      t        j"                  |
      dz  }dd|iz  }t%        t        j&                  |            dx}	x}}
y# 1 sw Y   _xY w)!u]   audit 파일에 손상된 JSON 라인이 있어도 _iter_rows 가 skip + 정상 라인 yield.r   r   DEDUPEDr   rz   r#   Fr   DEDUPEr   ar$   r%   z{this-is-not-json}
Nh   z/repos/o/r/issues/104/commentsr   rj   r   r   r   r   rd   re   r   r   r   r   r   rc   r   )r	   _ensure_parentr=   r   r   openr7   write_HEAD_Blistr   r   rS   rm   rU   rV   rW   rX   rY   rZ   )r*   rF   fhr   r[   rp   r\   rr   rs   r   rq   r   s               r-   (test_iter_rows_tolerates_corrupted_linesr   N  s   h'E		LL$$,8,"!#"	
  
ejj#	0 )B
'()	LL$$,8,"!#"	
   "#Dt99>933tt974=C=C=C=C74=C=C=C=C+) )s   KKc                   t        |       }t        |j                               }g }||k(  }|st        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  }t        t        j                  |            dx}}y)u7   audit 파일 미존재 시 _iter_rows 는 빈 iterator.rj   )z%(py0)s == %(py3)sr   )rL   rN   r   rO   N)r	   r   r   rS   rm   rU   rV   rW   rX   rY   rZ   )r*   rF   r   r[   ro   r   r]   s          r-   !test_iter_rows_empty_when_no_filer   {  sw    h'E  "#D42:42442r/   c                 `   ddl m}  | j                  }d}t        ||      }| }|s t	        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                  |       rt	        j                  |       ndt	        j                  |      t	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d	x}x}x}}y	)
uE   _resolve_owner_repo 메서드는 제거됨 (PR #104 baseline 대비).r   )owner_trigger_only_resolve_owner_repozGtask-2554+1 medium #2/#5: _resolve_owner_repo dead code must be removedza
>assert not %(py7)s
{%(py7)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.OwnerTriggerOnly
}, %(py5)s)
}rK   r   )rL   rM   rN   rO   rg   N)anu_v2r   r   rK   rS   rT   rU   rV   rW   rX   rY   rZ   )r   r[   r\   rt   ru   rs   s         r-   2test_resolve_owner_repo_method_removed_from_moduler     s   )):: <Q w:<QR RR R   	R               *    *    ;    =R    S      r/   c                    t         dz  dz  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  }d
d|iz  }t        t        j                  |            dx}}y)uM   정적 grep: owner_trigger_only.py 본문에 _resolve_owner_repo 정의 0건.r   owner_trigger_only.pyr$   r%   zdef _resolve_owner_repor   )z%(py1)s not in %(py3)ssrcr   r   rO   N
WORKSPACE_ROOTr   rS   rm   rX   rU   rV   rW   rY   rZ   r   r   r[   r   r]   s        r-   3test_module_source_no_resolve_owner_repo_definitionr     s}    H$'>>
I
ISZ
I
[C$/$C////$C///$//////C///C///////r/   c                    t         dz  dz  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  }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}}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}}dD ]  }|| v}|st        j                  d|fd|| f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |       rt        j                  |       nddz  }dd|iz  }t        t        j                  |            d} y)uQ   audit 본 파일 ("a" mode) 외에 sidecar lock 파일 ("a" mode lock_fh) 사용.r   owner_trigger_audit.pyr$   r%   zdef transactioninz%(py1)s in %(py3)sr   r   r   rO   Nrl   fcntl.LOCK_EXzopen(self._path, "a")zopen(self._path, "w"zopen(self._path, "r+"zopen(self._path, "a+"r   r   	forbiddenr   r   rb   r   )	r   r   r[   r   r]   r   ro   r   r   s	            r-   3test_audit_source_uses_sidecar_lock_for_transactionr     s   H$'??
J
JT[
J
\C########################;#;#;##!?c!!!!?c!!!?!!!!!!c!!!c!!!!!!!!(!S((((!S(((!((((((S(((S(((((((_ $	####y######y###y################$r/   c                    t         dz  dz  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  }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}}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}}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)uL   owner_trigger_only.py 가 audit.transaction() context 를 사용 (race fix).r   r   r$   r%   zself._audit.transaction()r   r   r   r   r   rO   Nztxn.check_deduper   r   r
   r   r   s        r-   .test_owner_trigger_only_uses_audit_transactionr     s   H$'>>
I
ISZ
I
[C&-&#----&#---&------#---#-------$$$$$$$$$$$$$$$$$$$$$$$$!?c!!!!?c!!!?!!!!!!c!!!c!!!!!!!!?c!!!!?c!!!?!!!!!!c!!!c!!!!!!!"s""""s"""""""""s"""s"""""""r/   c                `   t        |       }|j                  ddt        t        t        dddddd
       |j                         5 }t        j                  t              5  |j                  ddt        t        t        dddddd
       d	d	d	       d	d	d	       y	# 1 sw Y   xY w# 1 sw Y   y	xY w)
u(  transaction.record(POSTED) 가 audit 본 파일 LOCK_EX 도 잡아 legacy append() 와 직렬화.

    회귀 시나리오: sidecar lock 보호 외부에서 직접 ``OwnerTriggerAudit.append(POSTED)`` 가
    먼저 들어왔을 때 transaction.record(POSTED) 가 dedupe re-check 로 차단.
    r   r   r   rz   r#   Tr{   r|   N)
r	   r=   r   r   r   rJ   r   r   r   rR   r   s      r-   =test_transaction_record_double_lock_blocks_legacy_append_racer    s     h'E	LL$$#,8,!!+	
 
			 ]]?+ 	JJ,#,+$4 @%4%))3	 	 	 s$   B$)BB$B!	B$$B-c                    t         dz  dz  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  }d
d|iz  }t        t        j                  |            dx}}| 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  }
dd|
iz  }t        t        j                  |            dx}x}x}x}	}y)uT   정적 검사: record 메서드가 audit 본 파일 fcntl.flock(LOCK_EX) 를 사용.r   r   r$   r%   z!transaction atomic dedupe blockedr   r   r   r   r   rO   Nr   r   )>=)zK%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.count
}(%(py4)s)
} >= %(py9)s)rL   ra   rb   rc   rh   zassert %(py11)sri   )r   r   rS   rm   rX   rU   rV   rW   rY   rZ   count)r   r   r[   r   r]   ro   rq   rp   ru   @py_assert7@py_format10rw   s               r-   1test_audit_source_record_uses_audit_file_flock_exr    s   H$'??
J
JT[
J
\C.5.#5555.#555.555555#555#555555599*_*9_%**%****%******3***3***9***_***%***********r/   c                 |    ddl m}  t        j                  t              5   | d       ddd       y# 1 sw Y   yxY w)uP   _normalise_head 가 40-char 길이지만 비-hex 문자가 있으면 ValueError.r   _normalise_head(zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzNanu_v2.owner_trigger_auditr
  r   r   
ValueErrorr	  s    r-   2test_normalise_head_rejects_non_hex_40_char_stringr    s.    :	z	" "!" " "s   	2;c                 T   ddl m}  t        j                  t              5   | d       ddd       t        j                  t              5   | d       ddd       t        j                  t              5   | d       ddd       y# 1 sw Y   ^xY w# 1 sw Y   @xY w# 1 sw Y   yxY w)u&   길이가 40 이 아니면 ValueError.r   r	  abcN'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar  r	  s    r-   1test_normalise_head_rejects_short_or_long_stringsr    s    :	z	" 	z	" "!"	z	" "!" "	 " "" "s#   	B
	B4	BBBB'c            	        ddl m}  d} | |      }d}d}d}||z  }||z   }||k(  }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |       rt        j                  |       nd	d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}x}x}x}}y )Nr   r	  (ABCDEF0000000000000000000000000000000000abcdef0"   rj   )zI%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == (%(py6)s + (%(py8)s * %(py10)s))r
  r   )rL   rM   rN   rc   re   py10zassert %(py14)spy14)
r  r
  rS   rm   rU   rV   rW   rX   rY   rZ   )r
  r   r[   rp   r  @py_assert9@py_assert11@py_assert12r\   @py_format13@py_format15s              r-   4test_normalise_head_accepts_valid_hex_and_lowercasesr!    s    :D4 7H7s7R7sRx7Hx$77 $77777 $7777777?777?77777747774777 777H777s777R77777777r/   )r*   r   r   intr   rf   returnr   )r*   r   r1   rf   r2   zlist | None):__doc__
__future__r   builtinsrU   _pytest.assertion.rewrite	assertionrewriterS   r(   syspathlibr   r   __file__resolveparentsr   rf   r7   insertr  r   r   r   r	   r
   r   r   r   anu_v2.owner_trigger_onlyr   r   r   r.   rH   r^   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r!  rA   r/   r-   <module>r1     s-  $ #    
  h'')11!4~chh&HHOOAs>*+	 	 	 7 
 25' , 7 	
 6&3303./6.(24:!.H/8/* Z0$	#&R+"	"8r/   