# task-2783 — replacement_pr_runner truncate 근본수정 (bounded fix, ACTIVE=false)

## 레벨
Lv3 (테스트 격리 결함 + 코드 guard + 회귀). 코드 작업.

## allowed_resources (본 task의 capability)

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

## 목표 (한 줄)
QC full-suite pytest가 canonical repo 파일을 0바이트로 truncate하던 **근본 결함**을 수정한다: ①테스트 격리(실 repo 무접촉) + ②코드 guard(empty overwrite / repo root 밖 write fail-closed). **코드+테스트까지만.**

## 확정된 근본 원인 (단일 소스)
`memory/reports/infra_defect_two_axis_260702.md` + 증거 `memory/events/twcap_audit_260702_ROOTCAUSE.jsonl`.
- `test_replacement_pr_runner_2510.py`의 테스트(예: test_t01_clean_pr_no_op:101)가 **실제 `replacement_pr_runner.execute()`** 호출.
- `utils/replacement_pr_runner.py:186` `cwd = repo_dir or str(WORKSPACE)` — repo_dir 미지정 시 **canonical WORKSPACE로 fallback**.
- `:195` `target.write_text(sr.stdout)` — git show 결과 empty인데 **canonical `utils/replacement_pr_runner.py`+자기 test 파일에 0바이트 O_TRUNC write**.
- docstring이 "호출자는 tmp dir을 repo_dir로 줘야 source 손상 방지"라 경고하나 테스트가 위반.

## 수정 요구 (2겹 — GPT verbatim, 둘 다 필수)
**A. 테스트 격리** (`tests/regression/test_replacement_pr_runner_2510.py`)
- execute()/transplant를 호출하는 모든 테스트가 **tmp repo/tmp clone(tmp_path)** 을 repo_dir로 넘겨 **canonical `/home/jay/workspace`를 절대 쓰지 않게** 변경.
- 기존 통과 케이스 동작(19개) 보존.

**B. 코드 guard** (`utils/replacement_pr_runner.py::transplant_expected_files`) — 테스트만 고치면 부족, 코드 자체 방어 필수
- git show 결과가 **empty인데 기존 target이 non-empty**면 **fail-closed**(0바이트 덮어쓰기 거부, 명확한 예외).
- `target`이 의도한 **repo root(cwd) 밖으로 resolve**되면 즉시 fail.
- (canonical WORKSPACE로의 fallback이 위험하므로, 최소한 위 2 guard로 0바이트 파괴를 원천 차단.)

**C. 회귀 테스트** (기존 파일 내 방어 케이스 추가 권장)
- empty git-show 결과로 non-empty target 덮어쓰기 시도 → fail-closed 확인.
- target이 repo root 밖 resolve → fail 확인.
- (재발 방지: canonical/자기 test 파일 직접 write 경로가 다시 안 열림)

## 검증 (필수 — GPT 기준)
1. 문제 테스트 단독 PASS.
2. `pytest tests/regression/test_replacement_pr_runner_2510.py` **19개(또는 신규 포함 N개) PASS**.
3. ★ **canonical 2파일 size/sha 불변 증명**: 위 pytest 실행 **전/후** `utils/replacement_pr_runner.py`(33557B·sha 95809c89…)와 test 파일(24403B·sha 57ebdc51…) size/sha 비교 → **0바이트 미발생** 명시 보고.
4. expected_files가 **2파일(+선택 report)** 뿐인지 확인.

## ★★ 종료 검증 함정 (반드시 준수)
- **봇 자신의 최종 finish-task는 canonical(수정 전) finish-task→QC full-suite를 돌려 canonical 2파일을 또 truncate할 수 있다.** 이건 수정본이 아직 canonical에 merge 안 됐기 때문이며 **정상 예상**. 봇 자신의 finish-task 결과로 "canonical 안전해짐"을 주장 금지.
- 수정본 효과 증명은 **worktree isolated pytest + 2파일 size/sha 불변**으로만.
- 완료 후에도 상태는 **구현 후보(ACTIVE=false)**. canonical 보호는 merge 후 발효(별도 승인).

## ACTIVE=false 불변식 / 금지
- **PR 생성 금지 · merge 금지 · ci.yml/ruleset 0 · dispatch 추가 0 · real fire 0.**
- finish-task.sh/terminal_state_callback.py/gemini_evidence_verify.py 무접촉.
- 산출물 = 수정 2파일(+선택 report) + 로컬 pytest 통과 + worktree 커밋 + ANU 보고.

## 산출물 & 보고 (PR 없음 — ANU 보고까지만)
1. worktree 커밋. **PR 생성 금지.**
2. ANU 보고: 수정 파일 범위 / pytest 결과 / **2파일 size/sha 전후 불변 증거** / forbidden 무접촉.
3. 봇 종료 전 ANU callback(envelope만·UTF-8 ≤3900B). escalate로 끝나도 result.json/보고서 경로를 envelope에 남길 것.

## 검증 루프
자체 리뷰 1회 → pytest 통과 → 2파일 size/sha 불변 확인 → forbidden 무접촉 → 함정 준수.

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