# task-2783+2 — PR #262 same-PR microfix (Gemini symlink HIGH 해소)

## 레벨
Lv2 (2파일 정밀 microfix + 회귀 1~2케이스). 코드 작업. **PR #262 head에 커밋 추가**(신규 PR 금지).

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "utils/replacement_pr_runner.py"
    - "tests/regression/test_replacement_pr_runner_2510.py"
  forbidden_paths:
    - "utils/merge_queue_executor.py"
    - "utils/real_merge_hooks.py"
    - "scripts/finish-task.sh"
    - "scripts/gemini_evidence_verify.py"
    - "dispatch.py"
    - ".github/**"
    - ".claude/**"
    - ".env*"
    - "memory/state/**"
  commands:
    - "git"
    - "pytest"
    - "python3"
  merge_policy: "none"
  ttl_hours: 6
```

## 컨텍스트
PR #262(task-2783) head **1dc47e10**에 Gemini 재리뷰 **새 HIGH**(`utils/replacement_pr_runner.py:~208`, security-high): `transplant_expected_files`의 `target.write_text()`가 **target이 symlink이면 링크를 따라가 링크 대상 파일을 덮어씀**(symlink 자체를 regular file로 교체하지 않음) = correctness 버그. 회장/GPT 판정 = **정당한 지적, same-PR microfix로 해소**. 기존 브랜치 `task/task-2783-dev3`에 커밋 추가.

## 작업 위치
- 기존 worktree/브랜치 `task/task-2783-dev3`(head 1dc47e10)에서 작업 → 커밋 → `git push origin task/task-2783-dev3`로 **PR #262 갱신**. **신규 PR·merge 금지.**

## 수정 요구 (GPT verbatim — 처리 순서 준수)
`utils/replacement_pr_runner.py::transplant_expected_files`의 write 직전에 아래 순서 보장:
1. `resolved_target`(=`target.resolve()`)이 **repo_root 밖이면 `TARGET_OUTSIDE_REPO_ROOT` fail** (기존 Guard1 유지 — 순서상 **먼저**).
2. repo 안으로 확인된 `target`이 **symlink이면 `target.unlink()` 후 regular file로 write**. (핵심: `if target.is_symlink(): target.unlink()` 를 `write_text` 직전에)
3. repo **밖**을 가리키는 symlink는 1번(TARGET_OUTSIDE_REPO_ROOT)에서 먼저 fail → **삭제하지 않음**.
4. 일반 파일은 기존처럼 write.
5. **정상 empty blob 허용 / GIT_SHOW_FAILED fail-closed / repo-root escape fail 그대로 유지**(A안 성과 보존).
- ★ WORKSPACE fallback(`cwd = repo_dir or str(WORKSPACE)`)는 **이번에도 건드리지 말 것**(P1 별도). merge_queue_executor 무접촉.

## 회귀 테스트 (추가)
- **필수(내부 symlink replacement)**: tmp repo 안 victim 파일 생성 → `target`(expected path)을 victim으로 향하는 symlink로 생성 → transplant 실행 → ①target이 **regular file로 교체**됐는지 ②expected content가 target path에 쓰였는지 ③**victim 파일 내용 불변**인지 확인.
- **가능하면(외부 symlink)**: repo 밖 가리키는 symlink는 `TARGET_OUTSIDE_REPO_ROOT`로 fail + **symlink 미삭제** 검증.
- 기존 **T17/T17b/T18 유지**.

## 검증 (필수)
1. `pytest tests/regression/test_replacement_pr_runner_2510.py` 전체 PASS(개수 명시).
2. ★ pytest 실행 **전/후 canonical 2파일 size/sha 불변**(0바이트 미발생) 명시.
3. diff가 **2파일뿐**(merge_queue·forbidden 무접촉) 확인.
4. Gemini symlink HIGH 해소 근거(코드 순서) 설명.

## ★ 종료 검증 함정 (유지)
- 봇 자신의 finish-task는 canonical(미merge) 경로로 QC full-suite를 돌려 canonical 2파일을 또 truncate할 수 있음(정상 예상). 수정본 효과는 **worktree isolated pytest + size/sha 불변**으로만 증명. 완료해도 **구현 후보(ACTIVE=false)**.

## 금지
신규 PR · merge · merge_queue_executor/real_merge_hooks 접촉 · WORKSPACE fallback 손대기 · ci.yml/ruleset · thread 임의 resolve · 후속 task dispatch.

## 산출물 & 보고
1. 브랜치 `task/task-2783-dev3`에 커밋 + push(PR #262 갱신).
2. ANU 보고: diff(2파일) / pytest 결과 / canonical size/sha 불변 / symlink HIGH 해소 설명.
3. 봇 종료 전 ANU callback(envelope만·UTF-8 ≤3900B).

## goal_assertions (auto-generated)
- `pytest tests/regression/test_replacement_pr_runner_2510.py`
