# P1 설계 문서 — WORKSPACE fallback truncate 근절 (설계만, 코드 dispatch 금지)

상태명: `WORKSPACE_FALLBACK_TRUNCATE_LIVE_REPRODUCED_P1_DESIGN_READY_ACTIVE_FALSE`
작성: ANU 직접 (2026-07-02 KST). **설계 문서까지만 — 코드 dispatch 금지·merge 금지.** 근거: 회장 GPT 판정(microfix PASS 후보 / WORKSPACE fallback LIVE 재발 P1 격상 / 후속은 설계문서만 준비). 선행: [[project_replacement_pr_runner_truncate_hygiene_bug_recurred_260630]].

## 배경 (증거 강도별)
- **확정**: dev4(task-2783+1) finish-task 중 canonical `utils/replacement_pr_runner.py`+test가 **또 0바이트 truncate**됨(watcher LIVE 포착) → git checkout 복원 → 재실행 PASS. 
- **강한 추정**: PR push를 픽업한 **merge-readiness/real-merge simulation**이 `repo_dir` 없이 `transplant_expected_files`를 호출 → `cwd = repo_dir or str(WORKSPACE)` fallback으로 **canonical WORKSPACE에 직접 write**.
- **미확정**: 정확한 pid/stack/트리거 프로세스(A2 audit-hook과 달리 직접 스택 미포착). → **root cause confirmed 아님**, P1 실증 근거.
- microfix(A안, PR#262)는 **partial mitigation**: 테스트 tmp_path 격리 + Guard1(TARGET_OUTSIDE_REPO_ROOT) + GIT_SHOW_FAILED로 막지만, **repo_dir 미지정 시 WORKSPACE fallback 구조 자체는 미제거**. (오히려 EMPTY_OVERWRITE_REFUSED 제거로 그 경로 empty write가 더 자유로워짐 → P1 시급.)

## 핵심 결함 (한 줄)
`transplant_expected_files`(+형제 helper)의 **`cwd = repo_dir or str(WORKSPACE)` fallback** — 호출자가 repo_dir을 안 넘기면 canonical WORKSPACE에 write. `merge_queue_executor.py:1745`가 `ReplacementPRRunner(runner=..., dry_run=...)`를 **repo_dir 없이** 생성 → `self.repo_dir=None` → execute()→transplant가 WORKSPACE로 fallback.

## 설계 축 (P1 후속 task 범위 — 미확정 dispatch)
**축1. transplant/write 경로 WORKSPACE fallback 제거 or explicit allow flag**
- 옵션 A(권장): `repo_dir` **필수 인자화** — 미지정 시 `MISSING_REPO_DIR` fail-closed(WORKSPACE fallback 금지). write 계열(transplant·create_clean_replacement_branch·commit_local 등 `or str(WORKSPACE)` 3+곳) 일괄.
- 옵션 B: fallback 유지하되 **explicit allow flag**(`allow_workspace_write=False` default) — canonical WORKSPACE write는 flag 명시 시에만. 기본은 거부.
- ★ **정상 empty-file PR 허용 유지**(rc0+empty write는 정상 — A안 성과 보존). 이 축은 "canonical write 경로" 차단이지 "empty 내용" 차단 아님.

**축2. `merge_queue_executor.py:1745` 호출부 repo_dir 명시 주입**
- ReplacementPRRunner 생성 시 **명시적 tmp/sandbox repo_dir 전달**. 축1을 필수화하면 이 호출부가 안 고쳐지면 깨지므로 **동시 수정 필수**(coupled).
- CLI `replacement_pr_runner.py:706`(`ReplacementPRRunner(dry_run=...)`)도 동일 — in-scope 파일이라 같이.

**축3. dormant real-merge 경로 영향 검증**
- real-merge/merge_queue_executor는 현재 **ACTIVE=false(dormant)**. 축1/2가 dormant 경로 동작을 바꾸므로, 활성화 시 회귀 없는지 테스트로 고정. real 실행/활성화는 절대 금지(설계 단계).

## expected_files 후보 (dispatch 시 — 미승인)
- `utils/replacement_pr_runner.py`(fallback 제거/flag + CLI 호출부)
- `utils/merge_queue_executor.py`(1745 호출부 repo_dir 주입) ← **현 task-2783+1 forbidden이었던 파일 → P1에서 해제 필요**
- `tests/regression/test_replacement_pr_runner_2510.py`(+ merge_queue 관련 회귀)
- ★ scope가 real-merge/merge_queue로 확장되므로 **병렬 merge·real merge 축과 연결** → Gemini 신규 finding 가능성. 별도 승인·별도 PR 권장(PR#262와 분리).

## 실패 상태 enum (설계)
`MISSING_REPO_DIR`(옵션A) / `WORKSPACE_WRITE_NOT_ALLOWED`(옵션B) / 기존 `GIT_SHOW_FAILED`·`TARGET_OUTSIDE_REPO_ROOT` 유지.

## 테스트 케이스 (설계)
1. repo_dir 미지정 transplant → `MISSING_REPO_DIR`/거부 + canonical 무접촉.
2. merge_queue_executor 경로가 명시적 repo_dir로 동작(회귀 0).
3. 정상 empty-file PR(rc0+empty, repo_dir 명시) → 허용 유지.
4. git show 실패 → fail-closed 유지.
5. dormant real-merge 시뮬레이션이 WORKSPACE로 write 시도 → 거부(canonical 보호).

## 검증 (설계)
- pytest(replacement + merge_queue 관련) PASS.
- ★ finish-task/QC/merge-readiness 흐름 중 canonical 2파일 size/sha **불변**(LIVE truncate 재발 0) — 이게 P1 성공의 결정적 증거.
- Gemini 재리뷰 HIGH/CRITICAL 0.

## 우선순위 / 순서 (GPT)
- **P1(이 문서) > task-2782 > task-2781**. 이유: canonical 실제 파괴 경로 > 콜백 관측성(2782) > merge_group evidence(2781).
- 순서: (PR#262 Gemini HIGH 해소+CI green+회장 merge 승인 packet→merge) **직후** → 이 P1 후속 task **설계 재확인→회장 승인→dispatch**(별도 PR).

## 금지 (현 단계)
코드 수정·dispatch·PR·merge·merge_queue_executor 접촉·real-merge 실행/활성화·ci.yml/ruleset — 전부 미실행. **설계 문서까지만.**

## 상태
설계 준비 완료·대기. dispatch는 PR#262 처리 후 + 회장 별도 승인 시.
