
    xi
I                       U d Z ddlmZ ddlZddlZddlZddlZddlZddl	m
Z
 ddlmZ ddl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 ddlmZmZmZ d	Z d
e!d<   	 dZ"d
e!d<   	 edz  dz  dz  dz  Z#de!d<   	 edz  dz  Z$de!d<   	 dZ%de!d<   	 e fd*dZ&d+dZ'd,dZ(d-dZ)d.dZ*d/d Z+ejX                  j[                  dg d!      d0d"       Z.ejX                  j[                  d#g d$      	 	 	 	 	 	 	 	 d1d%       Z/d/d&Z0d.d'Z1d.d(Z2d.d)Z3y)2u  task-2488 Phase B PoC ``cycle_advancer`` 회귀 테스트.

본 모듈은 :mod:`tools.poc.cycle_advancer`의 5단계 분석 파이프라인 출력이

* deterministic (동일 입력 + 동일 fixed-timestamp → 동일 SHA-256)
* schema strict (YAML frontmatter 필수 필드 + 타입)
* safety-flag invariant (``proposal_only=True`` / ``ready_for_dispatch=False``)

세 조건을 모두 만족하는지 검증한다. PoC는 production lifecycle을 절대 건드려서는
안 되므로, 다음 forbidden 항목도 함께 보호한다:

* 실제 ``memory/events/task-XXXX.done`` / ``.escalate`` / ``.fail`` 생성 금지.
* ``scripts/`` / ``utils/`` / ``teams/`` / ``.github/`` 미터치.
* 외부 네트워크 호출 금지 (mock_ai_adapter 외 LLM 어댑터 미사용).

테스트는 모두 ``tmp_path`` fixture로 출력을 격리하여 실 ``memory/poc/`` 디렉토리를
오염시키지 않는다.
    )annotationsN)Path)Any   )DETERMINISTIC_SEEDGENERATOR_IDSCHEMA_VERSIONCycleAdvancerload_fixture)DraftPayloadrender_draftwrite_draftz2026-05-08T00:00:00ZstrFIXED_TIMESTAMP@d352e941f19a1620b08d4655d9644fc2056fd87779af0dde7281302d199a5333EXPECTED_SHA_TASK_2486toolspoccycle_advancerfixturesr   FIXTURE_DIRmemoryevents
EVENTS_DIR)scriptsutilsteamsz.githubtuple[str, ...]FORBIDDEN_DIRStask_idc                    t        t        |       }t               }|j                  |      }|j	                  |||      }t        |      S )uM  fixture에서 evidence를 로드하여 draft 본문(str)을 결정적으로 생성한다.

    Args:
        task_id: source task_id (fixture 파일 ``{task_id}.json``에 매칭).
        generated_at: ISO8601 + ``Z`` 형식 deterministic timestamp.

    Returns:
        :func:`render_draft`가 산출한 draft markdown 본문.
    evidenceanalysisgenerated_at)r   r   r
   analyzebuild_draft_payloadr   )r    r%   r#   advancerr$   payloads         4/home/jay/workspace/tests/poc/test_cycle_advancer.py_build_draft_textr+   M   sR      ,KAHH)H$88! 9 G
       c                    | j                  d      sJ d       | j                  dd      }t        |      dk(  sJ d       t        j                  |d         }t        |t              sJ d       |S )uV  draft markdown에서 YAML frontmatter 블록을 strict하게 파싱한다.

    Args:
        draft_text: ``render_draft`` 출력 (frontmatter는 항상 ``---`` 으로 둘러싸임).

    Returns:
        ``yaml.safe_load`` 결과 dict.

    Raises:
        AssertionError: frontmatter 구조가 비정상이거나 dict가 아닌 경우.
    z---
u0   frontmatter는 첫 줄이 '---' 이어야 한다r      uA   frontmatter 구분자 '---'가 정확히 두 번 나와야 한다   u1   frontmatter는 YAML 매핑(dict) 이어야 한다)
startswithsplitlenyaml	safe_load
isinstancedict)
draft_textpartsparseds      r*   _parse_frontmatterr:   b   sy       ) :) Wa(Eu:?___?^^E!H%Ffd#X%XX#Mr,   c                    t         j                         s
t               S t               }t         j                         D ]4  }|j                  t        fd| D              s$|j                         6 |S )u@  ``memory/events/``에서 주어진 prefix로 시작하는 파일명 집합을 반환한다.

    Args:
        prefixes: ``task-2486`` 같은 파일명 prefix 튜플.

    Returns:
        조건을 만족하는 파일명들의 집합. ``memory/events`` 디렉토리가 없으면
        빈 집합을 반환한다.
    c              3  @   K   | ]  }j                  |        y w)N)r0   ).0pnames     r*   	<genexpr>z(_snapshot_event_files.<locals>.<genexpr>   s     4atq!4s   )r   is_dirsetiterdirr?   anyadd)prefixessnapshotentryr?   s      @r*   _snapshot_event_filesrI   y   sa     uH##% zz4844LL Or,   c                 6   i } t         D ]x  }t        |z  }|j                         st        j                  |d      D ]B  \  }}}|D ]7  }t        |      |z  }	 |j                         j                  | t        |      <   9 D z | S # t        t        f$ r Y Qw xY w)u   forbidden 디렉토리 하위 파일들의 mtime snapshot을 반환한다.

    Returns:
        ``{relative_path_str: mtime}`` dict. 디렉토리가 존재하지 않으면 해당
        엔트리는 누락된다.
    F)followlinks)r   _WORKSPACE_ROOTexistsoswalkr   statst_mtimer   FileNotFoundErrorPermissionError)snapsubrootdirpath_	filenamesfnamefpaths           r*   _snapshot_forbidden_mtimesr\      s      D ${{}%'WWTu%E 	!GQ	" W-',zz|'<'<DU$	 K *?; s   &BB	B	c                >   | dz  }t        d      }t        j                  |j                  d            j	                         }|t
        k(  sJ dt
         d|        t        t        d      }t               }|j                  |      }|j                  ||t              }t        ||      }|j                         sJ |j                  d      |k(  sJ t        d      }	t        j                  |	j                  d            j	                         }
||
k(  sJ d       y	)
uL   동일 fixture + 동일 fixed-timestamp → 동일 SHA-256 (두 번 실행).zrun-1	task-2485utf-8z'task-2485 draft SHA mismatch: expected , got r"   )encodinguA   동일 입력에 대해 두 번 실행한 결과 SHA가 달라짐N)r+   hashlibsha256encode	hexdigestr   r   r   r
   r&   r'   r   r   is_file	read_text)tmp_path
output_dirtext_asha_ar#   r(   r$   r)   writtentext_bsha_bs              r*   +test_deterministic_output_task_2485_to_2486ro      s   G#J{+FNN6==12<<>E** 
12H1I Jg	* K5HH)H**H? + G ':.G??g.&888 {+FNN6==12<<>EE>^^^>r,   c            
     ^   t        d      } t        |       }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d	   d
u sJ |d   du sJ |d   du sJ |d   dk(  sJ |d   t        k(  sJ |d   t        k(  sJ |d   sJ d       d|v sJ |d   }t	        |t
        j                        r9|t        j                  ddddddt
        j                  j                        k(  sJ t	        |t              sJ |t        k(  sJ dt         | v sJ d       |d   J |d   t        k(  sJ y)uL   draft md의 YAML frontmatter 필수 필드와 타입을 strict 검증한다.r^   schemazcycle_advancer/v1source_task_idproposed_task_id	task-2486classificationMERGE_PENDING_DEPENDENCYproposal_onlyTready_for_dispatchFchairman_required	generatorzcycle_advancer/v1-mockdeterministic_seedu0   deterministic_seed는 비어있으면 안 된다r%   i        r   )tzinfozgenerated_at: uJ   원문 frontmatter에는 정확한 timestamp 문자열이 있어야 한다conflict_summaryN)r+   r:   r   r   r5   _dtdatetimetimezoneutcr   r   r	   )draftfmr%   s      r*   test_frontmatter_schema_strictr      s   k*E	E	"B h<....;... ![000#==== o$&&&"#u,,,!"e+++ k?6666k?l***"#'9999"#W%WW#
 Rn%L,-s||!Q1a(8(8 
 
 	
 
 ,,,,...O,-6 T6
  !))) h<>)))r,   r^   	task-2483task-2472+1c                    t        |       }t        |      }|d   du s
J |  d       |d   du s
J |  d       d|v sJ d|v sJ y	)
uR   모든 fixture 출력에서 ``proposal_only=True`` / ``ready_for_dispatch=False``.rw   Tu#   : proposal_only 안전장치 위반rx   Fu?   : ready_for_dispatch 안전장치 위반 (real dispatch 금지)zproposal_only: truezready_for_dispatch: falseNr+   r:   )r    r   r   s      r*   test_proposal_only_safety_flagsr      s     g&E	E	"Bo$& )67& "#u, )RS,
 !E)))&%///r,   z@source_task_id,expected_proposed_task_id,expected_classification))r^   rt   rv   )r   	task-2484CLOSE_LIFECYCLE_BLOCKED)r   task-2472+2WORKFLOW_REGEX_INCOMPATIBLEc                    t        |       }t        |      }|d   | k(  sJ |d   |k(  sJ |d   |k(  sJ |d   du sJ |d   du sJ y)	uZ  3개 fixture가 명세된 다음 task_id + classification으로 매핑되는지 검증.

    명세상 ``task-2483`` 은 ``MERGED_CLOSE_BLOCKED_EXTERNAL`` 또는 그에 준한
    분류를 갖는다. 현재 mock 매핑은 ``CLOSE_LIFECYCLE_BLOCKED`` 으로 표기하므로
    "준한 분류" 조건을 만족한다 — 둘 다 close lifecycle 차단 의미.
    ``task-2472+1`` 은 명세 표기 ``MERGE_PENDING_DEPENDENCY`` 에 준하는
    ``WORKFLOW_REGEX_INCOMPATIBLE`` 분류를 사용한다 (workflow regex가 chain
    의존성을 차단하는 본질이므로 dependency 차단 계열).
    rr   rs   ru   rw   Trx   FNr   )rr   expected_proposed_task_idexpected_classificationr   r   s        r*   test_three_fixtures_mappingr     s    , n-E	E	"B>111 !%>>>>#:::: o$&&&"#u,,,r,   c                 n   t         dz  } | j                         s
J d|         t        d      }| j                         }|j	                  d      }||k(  sJ d       t        j                  |      j                         }t        j                  |      j                         }||cxk(  r
t        k(  sJ  J y)uI   task-2485 결과가 ``expected-task-2486-draft.md``와 byte-exact 일치.zexpected-task-2486-draft.mdzexpected fixture missing: r^   r_   uP   draft byte mismatch — render_draft 결과가 expected fixture와 다릅니다.N)	r   rf   r+   
read_bytesrd   rb   rc   re   r   )expected_pathactualexpected_bytesactual_bytes
sha_actualsha_expecteds         r*   $test_output_matches_expected_fixturer   >  s    "??M  "P&@$PP"{+F"--/N==)L>) Z)
 -779J>>.1;;=L?)??????r,   c           	     N   d}t        |      }dD ]  }t        t        |      }t               }|j	                  |      }|j                  ||t              }t        || |j                  dd      z        }t        |      j                  t        |             rJ | d| d        t        |      }	|	|z
  }
|
t               k(  sJ dt        |
       d	       d
}| j                  d      D ]8  }|j                         s|j                  j!                  |      s0J d|         y)u  PoC 실행이 production lifecycle 파일을 만들지 않음을 보장한다.

    실행 전 ``memory/events/task-2486*`` / ``task-2484*`` / ``task-2472+2*``
    파일 snapshot을 떠두고, dry-run CLI를 (직접 호출 형태로) 실행한 뒤
    snapshot 차이가 0건이어야 한다. tmp_path를 출력 디렉토리로 사용해 실제
    PoC 산출물이 production 영역에 떨어지지 않게 한다.
    )rt   r   r   r   r"   +rX   u    : PoC 산출물이 tmp_path 밖(u   )에 기록됨u1   production lifecycle 파일이 새로 생성됨: uO    — PoC는 .done / .escalate / .fail / .merge-pending 등 절대 생성 금지)z.donez	.escalatez.fail*u-   PoC가 금지된 확장자 파일을 생성: N)rI   r   r   r
   r&   r'   r   r   replacer   r0   rB   sortedrglobrf   r?   endswith)rh   rF   beforer    r#   r(   r$   r)   outafter	new_filesforbidden_suffixesproduceds                r*   +test_no_real_done_or_dispatch_files_createdr   V  sS    9H"8,F = 
W5 ?##H-..( / 

 '8gooc3.G#GH3x""3x=1 	
i7uNK	
1
 "(+EI 
;F9<M;N OU 	U 9NN3' }}--.@A ?zJAr,   c           	        t               }dD ]d  }t        t        |      }t               }|j	                  |      }|j                  ||t              }t        || |j                  dd      z         f t               }|j                         |j                         z  }|D 	cg c]  }	||	   ||	   k7  s|	 }
}	|
r(t        j                  d|
dd  dt        |
       d	       |
g k(  sJ d
|
dd         yc c}	w )u  PoC 실행으로 forbidden 디렉토리 mtime이 변경되지 않는지 검증한다.

    환경 의존성이 있으므로 파일 추가/삭제로 인한 차이는 ``xfail``로 처리한다.
    그러나 mtime이 변한 파일은 단 1건도 허용하지 않는다.
    r   r"   r   rX   uD   forbidden 영역에 mtime 변동 감지 (외부 봇 영향 가능): Nr|   u    (총 u   건)u;   forbidden 영역 파일 mtime이 PoC 실행 중 변경됨: )r\   r   r   r
   r&   r'   r   r   r   keyspytestxfailr2   )rh   r   r    r#   r(   r$   r)   r   common_keyspathchangeds              r*   !test_forbidden_paths_not_modifiedr     s!    ()F = 	CW5 ?##H-..( / 

 	GXS(AAB	C '(E
 ++-%**,.K$td(CG  Rr{m6#g,t5	

 b= 
Egbqk]S=s   #C74C7c                   t         dz  dz  dz  }|j                         s
J d|        | dz  }t        j                  t        j
                  t        |      dddt        t              d	t        |      d
t        g
t        t               ddd      }|j                  dk(  s.J d|j                   d|j                   d|j                          t        |j                  d            }t        |      dk(  s
J d|        t        j                   |d   j#                               j%                         }|t&        k(  sJ dt&         d|        y)u7  ``cycle_advancer_dry_run.py`` CLI를 subprocess로 직접 실행하는 e2e 테스트.

    CLI 인자 (``--task-id`` / ``--fixture-dir`` / ``--output-dir`` /
    ``--fixed-timestamp``)가 정상 동작하고, draft md가 정확히 1개 생성되며,
    SHA-256이 expected와 일치하는지 확인한다.
    r   r   zcycle_advancer_dry_run.pyzCLI entry not found: zcli-outz	--task-idr^   z--fixture-dirz--output-dirz--fixed-timestampT   )cwdcapture_outputtexttimeoutr   u   CLI 실패: rc=z
stdout=z
stderr=zdraft-task-2486-*.mdr/   u9   draft md가 정확히 1개 생성되어야 함. produced=u"   CLI 출력 SHA mismatch: expected r`   N)rL   rf   
subprocessrunsys
executabler   r   r   
returncodestdoutstderrlistglobr2   rb   rc   r   re   r   )rh   cli_pathri   procr   shas         r*   test_dry_run_cli_e2er     s\    (503NNHA!6xjAAI%J>>NNM
O	
  !D& ??a 
$//*)DKK=	$++W JOO$:;<Hx=A 
CH:N ..!//1
2
<
<
>C(( 
,-C,DF3%P(r,   )r    r   r%   r   returnr   )r7   r   r   zdict[str, Any])rF   r   r   zset[str])r   zdict[str, float])rh   r   r   None)r   r   )r    r   r   r   )rr   r   r   r   r   r   r   r   )4__doc__
__future__r   r   r   rb   rN   r   r   pathlibr   typingr   r   r3   __file__resolveparentsrL   r   r   inserttools.poc.cycle_advancerr   r   r	   r
   r   &tools.poc.cycle_advancer.output_writerr   r   r   r   __annotations__r   r   r   r   r+   r:   rI   r\   ro   r   markparametrizer   r   r   r   r   r    r,   r*   <module>r      s  & #   	  
     x.((*2215sxx'HHOOAs?+,   . - \ G   S g%(88:E T  :"X-8
D 8 b"J J 9 9H !*.(:_B**d -0	0, F--"- !- 
	-->@0(`%Z+r,   