# task-2623 — REPLACEMENT_PR_V3_SHIM_AND_DRY_RUN_REGRESSION

- **task_id**: task-2623
- **work_level**: Lv.2 (LOW risk · ≤15 LOC + mock regression)
- **dispatched_by**: ANU (회장 결정 2026-05-21 ACCEPT)
- **assignee**: dev6 페룬 (task-2622 baseline 직후 동일 LOW risk · mock-based regression 카테고리)
- **base_decision_doc**: memory/events/anu_v3_pr_merge_partial_closure_dry_run.decision.json
- **base_proposal_docs**:
  - memory/events/replacement_pr_v3_adapter_proposal.json
  - memory/events/replacement_pr_dry_run_fixture_matrix.json
  - memory/events/replacement_pr_v3_gap_closure.result.json

---

## 0. PRE-DISPATCH SPEC (회장 verbatim 박제 · D-SPEC-EXACTNESS)

회장 verbatim 명시 (2026-05-21):

> 작업명: REPLACEMENT_PR_V3_SHIM_AND_DRY_RUN_REGRESSION
> 목표: utils/replacement_pr_runner.py empty gap을 닫고, v3 import path에서 replacement PR dry-run 판단이 가능하도록 최소 shim + regression을 만든다.

본 spec 은 회장 verbatim 11 필수 + 12 금지 + 7 완료 보고 항목을 절대 paraphrase 없이 박제한다.

---

## 1. 본질 §1 — 회장 명시 11 필수 항목 (verbatim)

1. utils/replacement_pr_runner.py가 더 이상 empty가 아니어야 함
2. v3 import path에서 dry-run entrypoint 접근 가능
3. expected_files exact match fixture
4. forbidden path fixture
5. effective diff contamination fixture
6. replacement PR failure → Critical7 fixture
7. same-branch push 금지 fixture
8. original PR preservation fixture
9. FDR1~FDR14 중 최소 핵심 fixture 우선 구현
10. Codex HIGH/CRITICAL 0
11. PR/merge/credential/write 0

---

## 2. 본질 §2 — 회장 명시 12 금지 항목 (verbatim)

- 실제 PR open
- branch 생성
- commit/push
- merge
- GitHub write
- OWNER PAT 사용
- replacement PR live mode 활성화
- reconcile evidence contract 수정
- Track C 수정
- zombie cron 정리
- (암묵 추가) `anu_v2/replacement_pr_runner.py` 수정 — sole source 유지 doctrine
- (암묵 추가) `dispatch/__init__.py` task-2621 wiring 9 markers 변경

---

## 3. expected_files (정확 일치 — 외부 변경 시 contamination)

- `utils/replacement_pr_runner.py` (0 lines → ≤15 LOC + docstring + __all__ thin re-export shim)
- `tests/regression/test_replacement_pr_v3_shim_dry_run.py` (신규 · mock-based · 실 GitHub 0 · subprocess 0)

**expected_files count = 2**. 이 외 파일 변경 시 effective_diff_contamination → REPLACEMENT_PR_REQUIRED 또는 Critical7.

---

## 4. forbidden_paths

- `.github/workflows/*` (전면 금지)
- `anu_v2/replacement_pr_runner.py` (sole source 유지)
- `anu_v2/merge_queue_executor.py` (의존 모듈 미수정)
- `utils/merge_queue_executor.py` (외부 결정 모듈 미수정)
- `dispatch/__init__.py` (task-2621 wiring 9 markers 변경 금지)
- `utils/post_merge_smoke_runner.py` (task-2624 영역 — 본 task 미접촉)
- `utils/lifecycle_reconciliation_manager.py` (task-2624 영역)
- 모든 credential 파일 (`.env*`, `*.pem`, `*.key`)

forbidden path 1건이라도 detected → 즉시 Critical7 보고 + halt.

---

## 5. 구현 §1 — utils/replacement_pr_runner.py thin re-export shim

설계 anchor: `memory/events/replacement_pr_v3_adapter_proposal.json`

본 파일은 v3 namespace 호환 shim 이다. 실 구현은 `anu_v2.replacement_pr_runner` sole source. 동작 변경 0. drift 위험 0.

필수 re-export symbols (11개):
- ContaminationReport, PreservationRecord, ReplacementResult, ReplacementFailure, ReplacementPRRunner
- REPLACEMENT_PR_CREATED, REPLACEMENT_PR_FAILED, ORIGINAL_PR_PRESERVED
- CRITICAL_REPLACEMENT_FAILED, CRITICAL_DIFF_REPLACEMENT_FAILED
- (선택 추가) build_executor_contract 정적 메서드 직접 export 불요 (클래스 통해 접근)

요구사항:
- ≤ 15 LOC (docstring + import + __all__ 포함 line 수 제한)
- `from __future__ import annotations` 권장 (단 LOC 제한 內)
- `from anu_v2.replacement_pr_runner import (...)` 단일 import block
- `__all__ = [...]` 명시
- v2 동작 변경 0 · v2 코드 미수정

---

## 6. 구현 §2 — tests/regression/test_replacement_pr_v3_shim_dry_run.py

설계 anchor: `memory/events/replacement_pr_dry_run_fixture_matrix.json`

mock-based regression. 실 GitHub API 호출 0 · subprocess 0 · 실 PR open 0.

핵심 fixture 우선 구현 (FDR matrix 발췌):

### 6.1 import smoke (회장 필수 #1, #2)
- `from utils.replacement_pr_runner import ReplacementPRRunner, ContaminationReport, ReplacementFailure, REPLACEMENT_PR_CREATED, CRITICAL_REPLACEMENT_FAILED, CRITICAL_DIFF_REPLACEMENT_FAILED` 가 ImportError 없이 통과
- v2 본 모듈과 class identity 등가 (`utils.replacement_pr_runner.ReplacementPRRunner is anu_v2.replacement_pr_runner.ReplacementPRRunner`) → True
- `__all__` 에 11 symbols 모두 포함

### 6.2 expected_files exact match (회장 필수 #3)
- FDR1 입력 (expected=actual={"src/a.py","src/b.py"})
- `runner.detect_contamination(...)` → `ContaminationReport(contaminated=False, extra_files=(), missing_files=())`

### 6.3 effective diff contamination (회장 필수 #5)
- FDR2 입력 (expected={"src/a.py"}, actual={"src/a.py","src/leaked.py"})
- → `ContaminationReport(contaminated=True, extra_files=("src/leaked.py",), missing_files=())`
- FDR3 입력 (expected={"src/a.py","src/b.py"}, actual={"src/a.py"})
- → `ContaminationReport(contaminated=True, extra_files=(), missing_files=("src/b.py",))`

### 6.4 forbidden path fixture (회장 필수 #4)
- expected_files 에 `.github/workflows/x.yml` 포함
- 본 shim 자체는 forbidden path 판정 안 함 (v3 external `merge_queue_executor.detect_forbidden_paths` 가 sole 결정자)
- 본 fixture 는 "shim 통과 후 외부 judgment 가 차단" docstring 명시 + skip 마커 또는 별도 모듈 호출 검증

### 6.5 replacement PR failure → Critical7 (회장 필수 #6)
- mock gh_runner/git_runner 가 stage="branch" 에서 returncode=1 반환
- `ReplacementFailure(stage="branch", reason="git_checkout_b_failed")` 생성 검증
- `runner.classify_failure(failure)` → `(CRITICAL_REPLACEMENT_FAILED, True)` (downstream)
- 별도: stage="bot_token" 시 `(CRITICAL_DIFF_REPLACEMENT_FAILED, True)` (precondition)

### 6.6 same-branch push 금지 (회장 필수 #7)
- v2 가 `assert_no_forbidden_git_flags` 를 stage args 별로 호출하므로, push args 에 `--force` 또는 `--admin` 주입 시 RuntimeError raise 검증
- 본 fixture 는 v2 의 guard 가 shim 경유해도 동작함을 입증 (이 검증 자체로 same-branch 시도 차단 doctrine 표명)

### 6.7 original PR preservation (회장 필수 #8)
- mock audit_writer · `runner.preserve_original_pr(999)` 호출
- 결과 `PreservationRecord(original_pr=999, preserved_state="OPEN", ...)`
- gh_runner / git_runner mock 호출 count == 0 검증 (gh/git 호출 0)

### 6.8 FDR1~FDR14 중 핵심 (회장 필수 #9)
- FDR1, FDR2, FDR3, FDR4(BOT_TOKEN 부재), FDR5(expected_files empty), FDR13(원본 보존) 6 fixture 최소 구현
- FDR6~FDR12 는 v2 본 모듈에서 이미 단위 테스트 존재 가정 — 본 shim test 에서는 import smoke + classify_failure 매핑 검증으로 충족

---

## 7. 14단계 finalize + 8 항목 직접 행동 (회장 doctrine 강제)

(2026-05-10 회장 직접 박제 doctrine. wrapper 가 처리 표현 절대 금지.)

### 7.1 finalize 14 단계
1. expected_files 정확 일치 검증 (외부 lint 1회)
2. forbidden_paths 검증 (외부 lint 1회)
3. unit/regression test PASS (pytest tests/regression/test_replacement_pr_v3_shim_dry_run.py · all pass)
4. 전체 test suite 영향 0 검증 (baseline regression 의 기존 PASS 카운트 유지 · BH1/BH2/BH3 기존 known issue 변동 0)
5. lint/type check (ruff/mypy/pyright 등 프로젝트 기본)
6. commit (BOT identity · token raw 노출 0 · force/admin/rebase 금지)
7. push origin <branch> (clean_branch_name 명시 · same-branch push 금지)
8. PR create (gh pr create --base main --head <branch> · 신규 PR)
9. CI 통과 대기 (모든 check SUCCESS)
10. Gemini review 1회 도착 후 처리 (post-Gemini same-PR push 금지 — 위반 시 replacement PR doctrine 진입)
11. mergeStateStatus CLEAN 확인
12. HEAD SHA lock 검증
13. squash merge (admin/force 금지 · OWNER_PAT 절대 금지)
14. post-merge smoke + reconcile 결선 (post_merge_smoke_runner 자동 호출 · evidence 부재 시 HOLD 유지)

### 7.2 봇 직접 행동 8 항목
1. task md 본문 읽기 · 9-R 검증 · 본질 변형 0
2. expected_files 외 파일 변경 0
3. forbidden_paths 1건이라도 탐지 시 즉시 Critical7 보고 + halt
4. credential value 로그/PR comment/cron prompt 평문 노출 0
5. BOT_GITHUB_TOKEN (ghs_ App token) 만 사용 · OWNER_PAT/ghp_ 절대 금지
6. same-branch push 금지 · 신규 clean branch 명시 (`task-2623-v3-shim-regression` 추천)
7. result.json 에 callback contract 9 fields 모두 PRESENT 보장
8. 봇 정상종료 직전 normal callback ANU key 발사 (ANU key c119085addb0f8b7 · 자기 key 절대 금지)

---

## 8. callback contract — 9 필수 fields (정책 (a) STANDARDIZED · UTF-8 ≤3900 bytes hard)

result.json 에 아래 9 fields 모두 PRESENT 필수:

1. `callback_prompt_utf8_bytes` (int · normal callback prompt UTF-8 byte 수 · `printf '%s' "$P" | wc -c` 측정)
2. `callback_prompt_chars` (int · normal callback prompt 자수)
3. `callback_cron_id` (string|null · 등록 성공 시 8자 hex · 실패 시 null)
4. `callback_registration_status` (string · "REGISTERED_FIRED_OK" / "REGISTERED_NOT_YET_FIRED" / "CALLBACK_PROMPT_TOO_LARGE" / "REGISTRATION_FAILED")
5. `callback_role` (string · "COLLECTOR_ANU" — 봇 자기 key 절대 금지)
6. `envelope_only_compliance` (bool · callback prompt 가 envelope 만 · 긴 본문 inline 0)
7. `fallback_prompt_utf8_bytes` (int · fallback safety-net prompt UTF-8 byte 수)
8. `fallback_safety_net_registered` (bool · 정상 등록 시 true)
9. `fallback_safety_net_role_single_purpose` (string · "RECOVERY_ONLY_NO_FINAL_REPORT_TRIGGER")

callback prompt UTF-8 ≤3900 bytes hard limit. 3500+ warning. 권장 target 2800~3200 bytes.

callback prompt 에 긴 본문 inline 금지. envelope 만 포함:
- `task_id` · `result_path` · `decision_path` · `report_path` · `sha256` · `collector_role=ANU` · `owner_key` · one-line summary

위반 시 등록 시도 금지 → `CALLBACK_PROMPT_TOO_LARGE` fail-closed.

---

## 9. doctrines (1차 필수)

### 9.1 anu_v2 sole source 유지
- `anu_v2/replacement_pr_runner.py` 변경 0 · 본 task 범위 절대 아님
- shim 은 re-export 만 · v3 path 에서 다른 동작 도입 시 drift = 즉시 Critical7

### 9.2 same-PR post-Gemini push doctrine (feedback_same_pr_push_after_gemini)
- Gemini review 도착 후 same-PR push/commit/amend 금지
- 위반 시 Option A replacement 또는 OWNER_DECISION_REQUIRED 분기

### 9.3 attempt-N hard limit doctrine
- chain resolve cycle 회장 결정 hard limit 외 자동 재시도 금지
- 본 task 는 attempt-1 만 · 신규 finding 시 회장 escalate

### 9.4 OWNER_TRIGGER_ONLY_CAPABILITY
- OWNER token 은 /gemini review 1회 작성 외 모든 action 코드 차단
- 본 task 봇은 BOT App token 만 사용

### 9.5 minimal scope vs spec compliance
- expected_files 2개 정확 · scope expansion 시 Critical7
- 단 spec compliance 위해 minimal patch 가 안 된다면 회장 escalate (paraphrase 금지)

---

## 10. 9-R 검증 anchor

본 task md sha256 은 dispatch cron prompt 에 명시. 봇은 본 md 본문을 수신·읽고 9-R 검증한다.

만약 본문에 ambiguity 또는 spec drift 발견 시:
- 변형 시도 금지
- 회장 직접 escalate (ANU 경유)
- attempt-N 재시도 금지

baseline_proposal_docs 와의 cross-check:
- `replacement_pr_v3_adapter_proposal.json` Option A · ≤15 LOC
- `replacement_pr_dry_run_fixture_matrix.json` FDR1~FDR14
- 본 md §5·§6 가 두 proposal 의 verbatim 구현 지시

---

## 11. 완료 보고 (회장 verbatim 7항목)

봇 result.json + report 가 아래 7항목 모두 답해야 한다:

1. v3 shim 적용 여부 (PASS/FAIL · `utils/replacement_pr_runner.py` LOC + sha256)
2. import path 확인 (`utils.replacement_pr_runner.ReplacementPRRunner is anu_v2.replacement_pr_runner.ReplacementPRRunner` True/False)
3. regression 결과 (pytest exit code · pass count · fail count · skip count)
4. dry-run matrix 결과 (FDR1/FDR2/FDR3/FDR4/FDR5/FDR13 PASS 명세)
5. Critical7 fixture 결과 (classify_failure(branch) → CRITICAL_REPLACEMENT_FAILED · classify_failure(bot_token) → CRITICAL_DIFF_REPLACEMENT_FAILED 둘 다 PASS)
6. 실제 write 0 증거 (실 PR open 0 · branch 생성 0 · commit/push 0 · merge 0 · GitHub write 0 · credential raw exposure 0)
7. 다음 reconcile evidence contract task 가능 여부 (본 task PASS 후 task-2624 reconcile 진입 가능 / 불가능 + 이유)

---

## 12. invariants (회장 verbatim · audit-only · 자동 차단)

본 task 실행 전·중·후 다음 invariants 모두 유지:

- read-only audit 끝났음 — 본 task 는 write task (코드/test 생성)
- 그러나 GitHub-level invariants 는 read-only 동일:
  - 실 PR open 0
  - 실 merge 0
  - branch push 1회 (clean_branch_name task-2623-v3-shim-regression)
  - commit 1회 (BOT identity)
  - GitHub write 1회 (PR create)
  - credential raw exposure 0
- token raw exposure 0
- `anu_v2/*` 변경 0
- `dispatch/__init__.py` task-2621 wiring 9 markers 변경 0
- `.github/workflows/*` 변경 0
- `utils/post_merge_smoke_runner.py` 변경 0 (task-2624 영역)
- `utils/lifecycle_reconciliation_manager.py` 변경 0 (task-2624 영역)
- self-key authoritative 0 (collector 는 ANU key only)
- recovery watcher 중복 spawn 0
- loop-boundary review 0
- Track C 미접촉
- zombie cron 미접촉
- CLOSED_ALL_SETTLED 산출물 byte-0

---

## 13. baseline anchors (sha256 검증용 · 변경 0)

- `anu_v2/replacement_pr_runner.py` — 442 lines · 18,695 bytes · mtime 2026-05-10 23:06:28 KST · task-2537 lineage · **본 task 변경 0**
- `dispatch/__init__.py` — task-2621 wiring 9 markers intact (L198-220 imports · L2910 Site #1 PRE-cron · L2984 POST-cron fallback · L3993 Site #2 PRE-cron · L4065 POST-cron fallback) · **본 task 변경 0**
- baseline regression test suite — BH1/BH2/BH3 기존 known issue 외 추가 fail 0 · **PASS 카운트 회귀 0**

---

## 14. attempt limit

- attempt-1 only · 자동 재시도 금지
- finding 시 회장 escalate (ANU 경유)
- HOLD/AUTO_REMEDIATION_HOLD 분기 시 회장 보고 후 명시 승인 대기

---

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "utils/replacement_pr_runner.py"
    - "tests/regression/test_replacement_pr_v3_shim_dry_run.py"
    - "memory/events/task-2623.result.json"
    - "memory/reports/task-2623.md"
    - "memory/tasks/task-2623.md"
  forbidden_paths:
    - "anu_v2/**"
    - "dispatch/**"
    - ".github/**"
    - "utils/merge_queue_executor.py"
    - "utils/post_merge_smoke_runner.py"
    - "utils/lifecycle_reconciliation_manager.py"
    - "utils/bot_merge_identity.py"
    - ".env*"
    - "*.pem"
    - "*.key"
  commands:
    - "pytest"
    - "python3 -m py_compile"
    - "ruff"
    - "git"
    - "gh"
  merge_policy: "tiered"
  ttl_hours: 24
```

## 15. 산출물

봇이 dev 직무로 생성:
- `utils/replacement_pr_runner.py` (수정 · 0 → ≤15 LOC)
- `tests/regression/test_replacement_pr_v3_shim_dry_run.py` (신규)
- `memory/events/task-2623.result.json` (callback contract 9 fields PRESENT + 7항목 완료 보고 dict)
- `memory/reports/task-2623.md` (human-readable 7항목 답변)
- PR (1개 · 신규 clean branch · same-branch push 금지)

---

## 16. callback envelope (참고)

봇 정상종료 직전 ANU normal callback cron 발사 시 envelope 만:

```
task_id=task-2623
result_path=memory/events/task-2623.result.json
decision_path=memory/events/anu_v3_pr_merge_partial_closure_dry_run.decision.json
report_path=memory/reports/task-2623.md
sha256=<task md sha256>
collector_role=ANU
owner_key=c119085addb0f8b7
summary=REPLACEMENT_PR_V3_SHIM_AND_DRY_RUN_REGRESSION result · 7항목 답변 result.json 참조
```

callback prompt UTF-8 byte 측정 후 result.json 기록. 초과 시 file-envelope 전환.

끝