
    1jN)                    X   d Z ddlmZ ddlZddlZddlZddlmZ ddlmZm	Z	 dZ
dZdZd#d	Zd
d fdd fdd fdd fdd fdd fdd fgZd$dZd%dZd&dZ	 d'	 	 	 	 	 d(dZ	 d'	 	 	 	 	 d(dZ ej(                  d      Zd)dZd*dZd+d Zed!k(  r e eej6                  d"d             y),u  v3.6 Runtime Harness — PR Diff Hygiene Guard (task-2716).

chair_authorization_id=CHAIR-AUTH-TASK-2716-PR-DIFF-HYGIENE-GUARD-260530

목적
----
runtime code PR diff 에 *미선언 artifact* 가 섞이는 것을 차단하는 allowlist-aware
가드. PR #156 / PR #161(evidence md 혼입) 재발 방지. 선언된 expected_files 는
false-positive 0 으로 통과시킨다.

Contract
--------
- classify_pr_diff(changed_files, expected_files) -> dict
    Returns {
        "status": "PASS" | "PR_DIFF_CONTAMINATED",
        "blocked": [<path>, ...],          # 차단된 미선언 artifact
        "reasons": {<path>: <why>, ...},   # 각 차단 파일이 왜 막혔는지
    }
    * status == PASS  → diff 가 선언된 expected_files(code + 선언 report)만 포함.
    * status == PR_DIFF_CONTAMINATED → 미선언 artifact 발견.

- detect_contamination(changed_files, expected_files) -> dict
    watcher 보조 결선용 read-only alias. classify_pr_diff 결과를 그대로 반환하되
    "detection_only": True 를 부가(보고 전용, 차단 아님).

- allowlist 는 *함수 인자*(expected_files)로만 받는다 — 하드코딩 금지.
  task md 의 expected_files 를 뽑아주는 헬퍼 extract_expected_files() 제공.

설계 원칙(회장 verbatim)
------------------------
1. allowlist-aware: memory/reports 전체 차단 금지. expected_files 에 명시된
   파일은 통과. expected_files 에 없는 artifact 패턴만 차단.
2. 결선: 주=finish-task.sh pre-push 차단, 보조=watcher read-only detection.
   CI workflow(.github/workflows/**) 는 본 가드가 절대 건드리지 않는다.
3. 결과 분류: PASS / PR_DIFF_CONTAMINATED.
    )annotationsNfnmatch)IterableOptionalz1CHAIR-AUTH-TASK-2716-PR-DIFF-HYGIENE-GUARD-260530PASSPR_DIFF_CONTAMINATEDc                P    | |j                  d      k(  xs | j                  |      S )N/)rstrip
startswith)	path_normprefixs     T/home/jay/workspace/tests/harness/../../scripts/harness/v36/pr_diff_hygiene_guard.py_has_prefixr   8   s&    c**Ji.B.B6.JJ    uK   memory/events/** (런타임 이벤트 마커 — PR diff 에 혼입 금지)c                    t        | d      S )Nzmemory/events/r   pbs     r   <lambda>r   ?   s    [$45 r   uF   memory/artifacts/** (artifact 산출물 — PR diff 에 혼입 금지)c                    t        | d      S )Nzmemory/artifacts/r   r   s     r   r   r   C   s    [$78 r   u;   callback-envelope json (콜백 봉투 — runtime artifact)c                    t        |d      S )Nz*callback-envelope*.jsonr   r   s     r   r   r   G   s    WQ :; r   u8   auth marker json (auth-path-marker — runtime artifact)c                    t        |d      S )Nz*auth*marker*.jsonr   r   s     r   r   r   K       WQ 45 r   uP   evidence md (PR #161 회귀 — evidence 문서는 expected_files 선언 필요)c                6    t        |d      xs t        |d      S )Nz*-evidence.mdz*fix-evidence*.mdr   r   s     r   r   r   O   s    WQ0SGA?R4S r   u5   temporary report (임시 리포트 — 선언 필요)c                6    t        |d      xs t        |d      S )Nz*temp*report*.mdz*tmp*report*.mdr   r   s     r   r   r   S   s    WQ 23TwqBS7T r   u5   cleanup evidence (정리 증거물 — 선언 필요)c                    t        |d      S )Nz*cleanup*evidence*r   r   s     r   r   r   W   r   r   c                    | j                         j                  dd      }|j                  d      r|dd }|j                  d      r|j                  d      s|j                  d      S |j                  d      S )u'   슬래시 정규화 + 선행 ./ 제거.\r   z./   N)stripreplacer   lstrip)pathr   s     r   
_normalizer'   \   se    

T3'A
,,t
abE ,,t
 !S 1188C=Dqxx}Dr   c                    |D ]J  }t        |      }|s| |k(  r yt        | |      r y|j                  d      s8| j                  |      sJ y y)uO   expected_files 에 의해 명시 허용되었는지(정확 일치 또는 glob).Tr   F)r'   r   endswithr   )r   allowentryes       r   _is_allowlistedr-   d   sX     u>9a ::c?y33A6 r   c                    | j                  dd      d   }t        D ]  \  }}	  || |      r|c S  y# t        $ r Y "w xY w)uE   artifact 차단 패턴에 걸리면 사유 문자열, 아니면 None.r      N)rsplit_ARTIFACT_RULES	Exception)r   basenamereasonpreds       r   _artifact_reasonr7   u   s^    Q'+H' 	Ix( )   		s   
3	??c                    t        |xs g       }g }i }| D ]M  }|t        t        |            }|st        ||      r*t	        |      }|8|j                  |       |||<   O |rt        nt        }|||dS )u^  PR diff 의 changed_files 를 expected_files allowlist 기준으로 분류.

    Args:
        changed_files: PR diff 에 포함된 변경 파일 경로 리스트.
        expected_files: 해당 task 가 선언한 allowlist(없으면 빈 리스트).

    Returns:
        {"status": PASS|PR_DIFF_CONTAMINATED, "blocked": [...], "reasons": {...}}
    )statusblockedreasons)listr'   strr-   r7   appendr	   r   )	changed_filesexpected_filesr*   r:   r;   rawr   r5   r9   s	            r   classify_pr_diffrB      s     %2&EG G (;s3x(	9e,!),NN9%!'GI(  &-!$FWEEr   c                (    t        | |      }d|d<   |S )uJ   watcher 보조 결선용 read-only alias — 보고 전용(차단 아님).Tdetection_only)rB   )r?   r@   results      r   detect_contaminationrF      s     
 m^<F#FMr   z['"]([^'"]+)['"]c                X   g }t        j                  d| t         j                        }|r|j                  d      j	                         D ]  }t        j
                  d|      }|rV|j                  d      j                         j                  d      j                  d      }d|v sd|v r|j                  |       t        j                  |      }|j                  |        t               }g }|D ]7  }	t        |	      }
|
s|
|vs|j                  |
       |j                  |
       9 |S )u   task md 의 expected_files 섹션(YAML 블록 또는 §Affected Files)에서 경로 수집.

    allowlist 소스는 전적으로 task 의 선언분이며, 본 함수는 그것을 파싱만 한다.
    z)expected_files:\s*\n(.*?)(?=\n#{1,3} |\Z)r/   z\s*-\s+(.+?)\s*$`z'"r   .)researchDOTALLgroup
splitlinesmatchr#   r>   _QUOTEDfindallextendsetr'   add)task_md_textpathsblocklinemtokenqmseenoutr   pns              r   extract_expected_filesr_      s   
 EIIBLRTR[R[\EKKN--/ 	D,d3A
((*005;;EB%<3%<LL'&BLL	 UDC ]"D.HHRLJJrN	
 Jr   c                x   | sg S | dk(  rt         j                  j                         }n=| j                  d      r*t	        | dd d      5 }|j                         }ddd       n| }t        j                  d      }|D cg c]#  }|j                         s|j                         % c}S # 1 sw Y   NxY wc c}w )uO   쉼표/개행/공백 구분 문자열 → 리스트. '-' 또는 '@file' 지원.-@r/   Nutf-8encodingz[,\n]+)sysstdinreadr   openrJ   splitr#   )argdatafpartsr   s        r   _read_linesro      s    	
czyy~~		#ab'G, 	668D	 	 HHY%E$2!	AGGI22	 	
 3s   
B+ B7B7+B4c                *   dd l }|j                  dd      }|j                  dd       |j                  dd	       |j                  d
d       |j                  ddd       |j                  ddd       |j                  |       }t	        |j
                        }t	        |j                        }|j                  r=	 t        |j                  d      5 }|t        |j                               z  }d d d        t        ||      }|j                   r!t        t!        j"                  |d             n|d   t$        k(  rt        dt'        |       d       n\t        dt'        |d          dt        j                         |d   D ])  }	t        d|	 d|d    |	    t        j                         + |j(                  ry|d   t$        k(  rdS d!S # 1 sw Y   xY w# t        $ r)}t        d| t        j                         Y d }~d }~ww xY w)"Nr   pr_diff_hygiene_guardz2allowlist-aware PR diff hygiene guard (task-2716).)progdescriptionz	--changeduF   변경 파일 목록(쉼표/개행 구분, '-'=stdin, '@path'=파일).)helpz
--expecteduJ   expected_files allowlist(쉼표/개행 구분, '-'=stdin, '@path'=파일).z	--task-mduF   task md 경로 — expected_files 를 자동 추출(allowlist 보강).z--detection-only
store_trueu?   watcher 보조 모드: 보고만 하고 exit 0(차단 안 함).)actionrt   z--jsonu   결과를 JSON 으로 출력.rc   rd   u'   [hygiene-guard] task-md 읽기 실패: )fileF)ensure_asciir9   u   [hygiene-guard] PASS — z file(s), contamination 0u<   [hygiene-guard] PR_DIFF_CONTAMINATED — 미선언 artifact r:   u   건 차단:z  - u     ← r;   r/   )argparseArgumentParseradd_argument
parse_argsro   changedexpectedtask_mdri   r_   rh   OSErrorprintrf   stderrrB   jsondumpsr   lenrD   )
argvry   apargsr}   r~   rm   r,   rE   r&   s
             r   _mainr      s   		 	 $H 
! 
B OOU   OOY   OOU   OON  
 OOH\8WOX==D$,,'G4==)H||	RdllW5 =21668<<=
 gx0Fyydjje45(t#-c'l^;TUV&&)&*;&<%=[JZZ
 y) UTF&	):4)@(ABTU x D(1/a/1= = 	R;A3?cjjQQ	Rs0   G  G5G  GG   	H)HH__main__r/   )r   r=   r   r=   returnbool)r&   r=   r   r=   )r   r=   r*   Iterable[str]r   r   )r   r=   r   Optional[str])N)r?   r   r@   zOptional[Iterable[str]]r   dict)rU   r=   r   	list[str])rk   r   r   r   )r   r   r   int)__doc__
__future__r   r   rJ   rf   r   typingr   r   CHAIR_AUTHORIZATION_IDr   r	   r   r2   r'   r-   r7   rB   rF   compilerP   r_   ro   r   __name__
SystemExitr    r   r   <module>r      sI  #H #  	 
  %L - K 	V5
 	Q8
 	F;
 	C5
 	[S
 	@T
 	@53@E"	 /3"F "F+"F 
"FN /3 + 
 "**,
-<370t z
U388AB<(
)) r   