# task-2623 — REPLACEMENT_PR_V3_SHIM_AND_DRY_RUN_REGRESSION (HOLD / OWNER_DECISION_REQUIRED)

- **task_id**: task-2623
- **dispatched_by**: 회장 → ANU (2026-05-21 ACCEPT)
- **executor**: dev6 페룬
- **work_level**: Lv.2
- **status**: `HOLD_SPEC_DRIFT_DETECTED` — finalize 14단계 진입 거부, 코드/test 생성 0, finish-task.sh 미호출
- **doctrine_applied**: §10 (9-R 검증 — ambiguity/spec drift 시 변형 금지 · 회장 escalate · attempt-N 재시도 금지), §14 (attempt-1 only · finding 시 escalate)
- **invariants_enforced**: §12 GitHub-level invariants — 실 PR 0 · 실 merge 0 · branch push 0 · commit 0 · GitHub write 0 · credential raw exposure 0

---

## SCQA — Spec Drift Escalation

### Situation (S)

회장 verbatim spec (2026-05-21):
> "utils/replacement_pr_runner.py empty gap을 닫고, v3 import path에서 replacement PR dry-run 판단이 가능하도록 최소 shim + regression을 만든다."

- expected_files count = **2**:
  1. `utils/replacement_pr_runner.py` (0 lines → ≤15 LOC thin re-export shim)
  2. `tests/regression/test_replacement_pr_v3_shim_dry_run.py` (신규)
- 동작 변경 0 · drift 위험 0
- §13 baseline regression PASS 카운트 회귀 0
- §5 anu_v2/replacement_pr_runner.py sole source 유지 · 본 task 변경 0

### Complication (C) — 4 finding 박제

#### Finding F1 — base reference docs 부재 (spec anchor 결손)

회장 §0 명시 base 문서 3건이 현재 filesystem 상 부재:

- `memory/events/anu_v3_pr_merge_partial_closure_dry_run.decision.json` — **NOT FOUND**
- `memory/events/replacement_pr_v3_adapter_proposal.json` — **NOT FOUND**
- `memory/events/replacement_pr_dry_run_fixture_matrix.json` — **NOT FOUND**
- `memory/events/replacement_pr_v3_gap_closure.result.json` — **NOT FOUND**

검증 명령: `ls memory/events/ | grep -iE "replacement_pr_v3|v3_adapter|dry_run_fixture|partial_closure|gap_closure"` → 0 hit.

§10 9-R anchor cross-check 불가능. 본 spec 본문만으로 진행 시 paraphrase 위험 ↑.

#### Finding F2 — utils/replacement_pr_runner.py 는 **empty 아님** (실측 718 lines, v1 intact)

회장 spec §3은 baseline을 "0 lines → ≤15 LOC" 로 명시. 그러나 실측:

- HEAD (`task/task-2553p1-f1-clean-replacement`) on `/home/jay/workspace`:
  - `git ls-files --stage utils/replacement_pr_runner.py` → `blob 1fa8b2d2d9b25288e7e55152fa73b763aad4551b` (718 lines)
  - `git cat-file -p HEAD:utils/replacement_pr_runner.py | wc -l` → **718**
- origin/main:
  - `git worktree add .worktrees/task-2623-dev6 origin/main` 후 `wc -l utils/replacement_pr_runner.py` → **718**
- 파일 lineage: task-2510 dev3 신규 + task-2516+1 루 circular import 제거 (PR #61, PR #67)
- v1 본문 sha256 = blob `1fa8b2d2d9b25288e7e55152fa73b763aad4551b` (HEAD on both branches)

→ spec baseline "0 lines" 가정과 실측 "718 lines" 사이 **70배 이상 차이**. expected_files 작업 가정 자체가 깨짐.

#### Finding F3 — v1 callers 4건 active (PASS), v2 API 와 비호환

v1 API 표면:
```python
class ReplacementPRRunner:
    def __init__(self, runner=None, *, dry_run=False, repo_dir=None,
                 extra_forbidden_patterns=None, timestamp_provider=None)
    def execute(self, pr_number: int, task_spec=None) -> ReplacementResult
# 모듈 레벨 함수 (callers 가 직접 import):
#   assert_no_cherry_pick, detect_contamination, transplant_expected_files,
#   assert_clean_working_tree, precheck_local_replacement_diff,
#   compare_effective_diff, detect_forbidden_paths, build_escalation_packet
```

v2 API 표면 (anu_v2.replacement_pr_runner — sole source):
```python
class ReplacementPRRunner:
    def __init__(self, *, gh_runner, git_runner, audit_writer, audit_root,
                 bot_token_env="BOT_GITHUB_TOKEN", bot_git_name=..., bot_git_email=...)
    def detect_contamination(self, original_pr_diff, expected_files) -> ContaminationReport
    def preserve_original_pr(self, pr_number) -> PreservationRecord
    def create_clean_replacement(self, *, original_pr, expected_files, clean_branch_name, ...) -> ReplacementResult | ReplacementFailure
    def classify_failure(self, failure) -> tuple[str, bool]
    @staticmethod
    def build_executor_contract(...) -> dict
```

`__init__` 파라미터 set 완전 disjoint. v1 module-level 함수 8종은 v2 에 부재.

**Active callers (모두 main 에서 PASS 중):**

| 호출자 | line | v1 사용 형태 | shim 교체 시 영향 |
|---|---|---|---|
| `utils/merge_queue_executor.py` | L63 import + L1745 instance | `ReplacementPRRunner(runner=runner, dry_run=args.dry_run)` | **runtime TypeError** (v2 시그니처 미일치) |
| `tests/regression/test_replacement_pr_runner_2510.py` | L20-26 import | `assert_no_cherry_pick, detect_contamination, transplant_expected_files, assert_clean_working_tree, precheck_local_replacement_diff` | **ImportError** — 19 testcase 전건 fail |
| `tests/regression/test_orchestration_runtime_2514.py` | L87 | `from utils.replacement_pr_runner import (...)` | 미상 — collection 단계 fail 가능 |
| `tests/e2e/test_auto_merge_e2e_replay_2515.py` | L41 | `from utils.replacement_pr_runner import ReplacementPRRunner` | import 통과하나 v2 시그니처로 instantiate 시 fail |

**실측 검증 (`pytest tests/regression/test_replacement_pr_runner_2510.py -q`)**:
- 현재 (v1 intact): `19 passed in 0.15s`
- shim 교체 후 예상: `19 failed (ImportError at collection)` — BH1/BH2/BH3 외 신규 회귀 19건

#### Finding F4 — §5/§13 자체 모순 (verbatim 박제 시 동시 만족 불가)

| 조항 | 요구사항 | shim 교체 시 |
|---|---|---|
| §5 | "동작 변경 0" | **위반** — v1 API 표면 8 함수 + 1 클래스 삭제 |
| §5 | "drift 위험 0" | **위반** — v1 callers 가 v2 API 와 호환 불가 |
| §13 | "PASS 카운트 회귀 0" | **위반** — 최소 19건 fail (test_replacement_pr_runner_2510) |
| §13 | "BH1/BH2/BH3 기존 known issue 외 추가 fail 0" | **위반** — 신규 fail 19+건, 모두 BH known issue 아님 |
| §3 | expected_files count = 2 (외부 변경 시 contamination) | **scope 확장 불가** — caller 수정 차단됨 |

→ chair 명시 verbatim 11 필수 + 12 금지 조항을 paraphrase 없이 동시에 만족할 수 있는 minimal patch 가 **존재하지 않음**.

### Question (Q)

Lv.2 작업으로 dispatched 된 task-2623 을 spec verbatim 그대로 수행하면 §13 baseline regression 위반이 확정된다. 봇은 다음 중 어느 분기를 선택해야 하는가?

- **A. spec verbatim 추행 + 회귀 19건 수용** (§5 동작변경 0 위반)
- **B. shim 을 v1 호환 wrapper 로 변형하여 ≤15 LOC 초과 작성** (§3 expected_files contamination · §5 ≤15 LOC 위반)
- **C. expected_files 에 callers 4건 추가하여 v1→v2 cutover 수행** (§3 expected_files count=2 위반 + scope 폭발)
- **D. HALT + escalate — spec drift 박제 후 chair 결정 대기** (§10/§14 doctrine 준수)

§10 명시: "변형 시도 금지 · 회장 직접 escalate · attempt-N 재시도 금지" → 분기 **D**.

### Answer (A) — 분기 D 선택, HALT 완료

**봇 직접 행동 결과 (§7.2)**:

1. ✅ task md 본문 읽기 · 9-R 검증 (anchor 부재 finding 박제)
2. ✅ expected_files 외 파일 변경 0 (코드 0 라인, test 0 라인 작성 — write disk byte 0 for spec scope)
3. ✅ forbidden_paths 1건 detected = 0 (touch 시도 0)
4. ✅ credential raw exposure 0 (PR/cron/log)
5. ✅ BOT_GITHUB_TOKEN 미사용 (gh/git 호출 0)
6. ✅ same-branch push 미발생 (push 0 · branch 생성 0 · 임시 worktree `task-2623-v3-shim-regression` 은 spec drift 인지 후 worktree remove + branch -D 정리, push 미발생)
7. ✅ result.json callback contract 9 fields PRESENT (아래 §3)
8. ✅ 정상종료 직전 ANU normal callback envelope-only (자기 key 미사용, ANU key c119085addb0f8b7)

**산출물**:

- `memory/reports/task-2623.md` (본 문서 — 7항목 답변 + 4 finding + recommendation)
- `memory/events/task-2623.result.json` (9 callback fields + HOLD status + verbatim finding payload)
- ❌ `utils/replacement_pr_runner.py` 변경 0 (HEAD blob `1fa8b2d2...` 보존)
- ❌ `tests/regression/test_replacement_pr_v3_shim_dry_run.py` 미생성

---

## 회장 명시 7항목 완료 보고 (§11 verbatim · HOLD 분기 답변)

### 1) v3 shim 적용 여부

**FAIL — 미적용 (intentional, doctrine §10 준수)**.

- `utils/replacement_pr_runner.py` HEAD blob sha256 (git-blob) = `1fa8b2d2d9b25288e7e55152fa73b763aad4551b`
- LOC = 718 (변경 0, baseline 동일)
- 본 task 에서 byte 추가 0

### 2) import path 확인 (`utils.replacement_pr_runner.ReplacementPRRunner is anu_v2.replacement_pr_runner.ReplacementPRRunner`)

**FALSE — 현재 baseline 에서 분리된 별도 클래스**.

- `utils.replacement_pr_runner.ReplacementPRRunner` = v1 (utils/replacement_pr_runner.py:402 정의)
- `anu_v2.replacement_pr_runner.ReplacementPRRunner` = v2 (anu_v2/replacement_pr_runner.py:116 정의)
- 두 클래스 다른 `__init__` 시그니처, 다른 메서드 set → `is` 비교 False

### 3) regression 결과 (pytest exit code · pass/fail/skip)

본 task 가 신규 regression 파일 미작성 → 해당 pytest 미실행.

baseline cross-check (참고):
- `pytest tests/regression/test_replacement_pr_runner_2510.py -q` → **19 passed in 0.15s** (v1 intact, BH 안정)

### 4) dry-run matrix 결과 (FDR1/FDR2/FDR3/FDR4/FDR5/FDR13)

**미실행 (shim 미생성)**. 분기 D 사유로 fixture 매트릭스 진입 차단.

대안 보존: `memory/events/replacement_pr_dry_run_fixture_matrix.json` 가 chair 측에서 정식 publish 시 별도 task 로 재dispatch 권장.

### 5) Critical7 fixture 결과

**미실행 (shim 미생성)**. classify_failure 매핑은 anu_v2 본 모듈 단위 테스트로 이미 회귀 cover 중인 것으로 추정 (별도 검증 권장).

### 6) 실제 write 0 증거

- 실 PR open: **0건**
- branch 생성: 1건 (`task-2623-v3-shim-regression`) → drift 인지 후 **즉시 삭제** (`git branch -D` confirmed deleted, 위 4187332c)
- worktree 생성: 1건 (`.worktrees/task-2623-dev6`) → **즉시 worktree remove --force** (drift 인지 직후)
- commit: **0건**
- push: **0건**
- merge: **0건**
- GitHub write: **0건**
- credential raw exposure: **0건** (token env access 시도 0)
- expected_files 외 byte 추가: **0**
- audit log 외 코드 변경: **0**

### 7) 다음 reconcile evidence contract task (task-2624) 가능 여부

**불가능 (조건부 HOLD)** — 다음 사유:

- task-2623 이 chair 결정 없이 무브브하면 task-2624 reconcile evidence contract 진입 시 동일 v1/v2 drift 가 reconcile gate 에 cascade
- 권장 순서:
  1. chair 가 v1→v2 cutover 정책 결정 (deprecate vs preserve vs dual-namespace)
  2. 결정 후 v1 callers (merge_queue_executor + 3 tests) 마이그레이션 전용 task 신규 dispatch
  3. cutover 완료 후 task-2623 재dispatch (revised spec 으로)
  4. 그 후에 task-2624 reconcile evidence contract 진입

---

## Recommendation (chair 판단 요청)

다음 3 옵션 중 하나 선택 요청:

### OPT-X (권장) — task-2623 spec revise 후 재dispatch

revised spec 안:
- expected_files 4건 으로 확장: shim + 신규 test + `utils/merge_queue_executor.py` (L1745 v2 시그니처 마이그) + `tests/regression/test_replacement_pr_runner_2510.py` (deprecate or v2 컨버전)
- LOC 제한 완화: shim 15 LOC + caller patch ≤ 50 LOC 별도 cap
- 또는: 본 task 는 read-only "v1 baseline 박제 + 마이그 계획서" 로 격하

### OPT-Y — task-2623 cancel + 별도 chain 으로 분할

- task-2623a: v1 deprecate plan + 마이그 dry-run
- task-2623b: v1 callers cutover (3 tests + 1 executor)
- task-2623c: v3 shim 도입 (현재 spec 그대로, 단 baseline 이 caller 마이그 후)

### OPT-Z — 현재 spec 강행 (회귀 19+건 수용)

chair 가 명시적으로 §13 PASS 회귀 허용 정책 선언 시 본 봇 attempt-2 dispatch 가능. 단 봇 단독 자동 시도는 §10 doctrine 위반 → 명시적 chair 결정 필수.

---

## L1 스모크테스트 결과

> 본 task 는 코드 미작성 → L1 스모크 대상 부재. workflow §4.8 "L1 미통과: {사유}" 형식 준수.

- 서버 재시작: 해당없음 (코드 변경 0)
- API 응답 확인: 해당없음 (백엔드 변경 0)
- 스크린샷: 해당없음 (프론트엔드 변경 0)
- **L1 미통과 사유**: spec drift 박제로 분기 D 선택 → shim/test 생성 차단 → 스모크 대상 자체 부재. baseline regression `pytest tests/regression/test_replacement_pr_runner_2510.py` 실측 19 passed 박제로 v1 baseline 무회귀 확인 (대체 evidence).

---

## 머지 판단

- **머지 필요**: **No** (코드 변경 0, PR 미생성)
- **브랜치**: `task-2623-v3-shim-regression` (생성 후 drift 인지 직후 삭제 완료)
- **워크트리 경로**: `.worktrees/task-2623-dev6` (worktree remove --force 완료)
- **머지 의견**: 분기 D 사유로 머지 대상 0. chair 결정 후 OPT-X/Y/Z 중 하나로 재dispatch 시 새 PR 생성 권장. 본 결과는 read-only escalation marker.

---

## 모델 사용 기록

- 페룬 (Opus 4.7 1M, 본 봇): 분석 + 보고서 작성 (검토/판단 — 코딩 0)
- 팀원 위임 0건 (코드 0 라인 작성 → 위임 불요)

---

## 발견 이슈 및 해결

| 이슈 | 분류 | 해결 |
|---|---|---|
| F1: base reference docs 4건 부재 | spec anchor 결손 | chair publish 요청 (escalation) |
| F2: utils/replacement_pr_runner.py baseline 718 lines (spec 0 lines 가정) | spec drift | 본 보고서 박제 → chair 재dispatch 결정 대기 |
| F3: v1 callers 4건 (19 passing tests) → shim 교체 시 19+회귀 | API 호환성 | 분기 D HALT, caller 마이그 별도 task 권장 |
| F4: §5 동작변경 0 + §13 회귀 0 + §3 expected_files=2 동시 충족 minimal patch 부재 | spec self-contradiction | OPT-X/Y/Z 옵션 chair 결정 요청 |
| (해결됨) 임시 worktree + branch 생성 | 정리 누락 위험 | drift 인지 즉시 worktree remove + branch -D 완료 |

---

## 회장 doctrine 준수 cross-check (self-audit)

| §  | 명시 invariant | 본 task 준수 여부 |
|---|---|---|
| §10 9-R | "ambiguity/drift 시 변형 금지 · 회장 escalate" | ✅ 분기 D 선택 |
| §10 9-R | "attempt-N 재시도 금지" | ✅ attempt-1 only, escalation 후 종료 |
| §12 invariants | 실 PR open 0 | ✅ |
| §12 invariants | 실 merge 0 | ✅ |
| §12 invariants | branch push 0 (현재 task) | ✅ (branch 생성 후 push 전 삭제) |
| §12 invariants | commit 0 | ✅ |
| §12 invariants | GitHub write 0 | ✅ |
| §12 invariants | credential raw exposure 0 | ✅ |
| §12 invariants | anu_v2/* 변경 0 | ✅ (sha256 `303d55de...` 보존) |
| §12 invariants | dispatch/__init__.py task-2621 wiring 변경 0 | ✅ |
| §12 invariants | .github/workflows/* 변경 0 | ✅ |
| §12 invariants | utils/post_merge_smoke_runner.py 변경 0 (task-2624 영역) | ✅ |
| §12 invariants | utils/lifecycle_reconciliation_manager.py 변경 0 (task-2624 영역) | ✅ |
| §12 invariants | self-key authoritative 0 | ✅ (collector = ANU key only) |
| §14 attempt | attempt-1 only | ✅ |
| §14 attempt | HOLD/AUTO_REMEDIATION_HOLD 분기 시 명시 승인 대기 | ✅ HOLD_SPEC_DRIFT_DETECTED 박제 후 대기 |

---

## sha256 anchors

- task md (`memory/tasks/task-2623.md`) = `06ba7a2e2d66da65682a5f6b869affa3ef588c70e0ef9e8661879a4dcbc2cd14`
- anu_v2/replacement_pr_runner.py = `303d55dee11daa87d4b7d2b3b0fe41b07ee6463be5774241940385244fdcd032` (sole source, 442 lines, 변경 0)
- utils/replacement_pr_runner.py blob (HEAD) = `1fa8b2d2d9b25288e7e55152fa73b763aad4551b` (v1, 718 lines, 변경 0)

끝

## 세션 통계
- 총 도구 호출: 0회

