---
task_id: task-2514
type: context
scope: task
created: 2026-05-09
updated: 2026-05-09
status: in-progress
---

# 맥락 노트: task-2514

**task**: task-2514

---

## 결정 근거

### 1. wiring을 evaluate_pr() / verify_head_lock_then_merge() 안에서 직접 수행
- 두 함수가 이미 5 모듈의 hook 자리(REPLACEMENT_PR_RUNNER_HOOK / AUTO_GEMINI_TRIAGE_HOOK / POST_MERGE_SMOKE_HOOK / CRITICAL_ESCALATION_HOOK)를 string으로 박제해 두었으므로 그 자리에 실제 호출을 끼워 넣는 것이 minimal change.
- 대안 (별도 orchestrator 클래스 신설) 기각 이유: 회장 §"새 abstraction 생성 금지" 위반.

### 2. ReplacementPRRunner / triage_pr / run_post_merge_smoke / process_event 모두 dependency injection 가능
- 5 모듈 모두 runner / dry_run / no_audit 옵션을 받도록 설계되어 있어, 회귀 테스트에서 mock injection이 가능함 (task-2510~2513의 회귀 테스트 패턴 그대로 사용).
- 따라서 wiring 시 ExecutorContext에 5 모듈 hook을 옵션으로 받도록 확장. 기본값은 실제 함수 호출.

### 3. CLI 출력 JSON에 신규 5 필드 추가하되 기존 필드 무수정
- 회장 §"task-2509 본체 인터페이스 breaking change 금지" 정합.
- pipeline_step / replacement_used / triage_summary / smoke_envelope / escalations 필드 모두 default=None/[]로 추가.
- QueueDecision dataclass에 필드 추가 — 기존 필드 보존 + asdict() 자동 직렬화.

### 4. to_legacy_gemini_state()로 기존 fetch_gemini_status() 결과 형식 대체
- triage_pr()는 TriageReport를 반환하므로 to_legacy_gemini_state()로 변환하면
  기존 evaluate_pr()의 gemini_state dict 인터페이스와 호환됨.
- 기존 분기 (status == "auto_triage_candidate" / "critical_scope_expansion" / "ok") 그대로 유지.
- 추가로 review_gate_status.review_gate_passed가 True인 경우만 gate 통과 (auto_resolve 후 0 unresolved).

### 5. emit_critical_escalation()에서 process_event() 호출
- 기존 emit_critical_escalation()이 reporter_hook 콜백을 받도록 박제되어 있음.
- 새 코드: reporter_hook이 None이면 default로 process_event(dry_run=True, no_audit=False) 호출.
- LEGACY_CRITICAL_MAP에 의해 기존 7종 코드가 자동으로 canonical CriticalEscalationType으로 매핑됨.
- 따라서 evaluate_pr() 내부의 emit_critical_escalation() 호출 코드는 무수정.

### 6. envelope 패턴: PostMergeSmokeRun을 verify_head_lock_then_merge에서 사용
- 기존 run_post_merge_smoke()는 dict {"status", "stdout", ...} 반환.
- 새 utils/post_merge_smoke_runner.run_post_merge_smoke()는 PostMergeSmokeRun 반환 (allow_continuation, escalation, stale 포함).
- 기존 함수 이름 충돌 → import alias: `from utils.post_merge_smoke_runner import run_post_merge_smoke as run_pm_smoke_v2`.
- task spec smoke_command 미정의 시 dry_run=True → SKIPPED + allow_continuation=True (회장 §10 정합).

## 3 Step Why 자문 (회장 G1 게이트)

### 1st Why: 왜 이 설계가 필요한가?
**A 답변**: 5 모듈이 각자 main에 머지되었지만, 단일 runtime pipeline으로 호출되지 않으면 "queue가 스스로 흐르는" 자동화 목표가 미충족이다. 회장이 마지막 wiring task로 분리한 이유.

### 2nd Why: 왜 A가 최선의 접근인가?
**B 답변**: 새 abstraction 생성 없이 evaluate_pr()/verify_head_lock_then_merge() 안의 기존 hook 자리에 실제 호출을 끼워넣는 방식이 회장 §금지(새 abstraction 0)를 정확히 만족시키면서 동시에 기존 회귀 16+24+14건을 보존한다. 별도 orchestrator를 만들면 dispatch 흐름과의 충돌 + 회귀 깨짐 위험.

### 3rd Why: 왜 B가 다른 대안보다 나은가?
**C 답변**: dispatch.py에 wiring을 두는 대안은 회장 §"dispatch.py 대규모 리팩토링 금지" 위반. 별도 cron/runner를 만드는 대안은 새 abstraction 생성 위반. 5 모듈 각자에 cross-module call을 추가하는 대안은 5 모듈 본체 수정(READ ONLY 위반). evaluate_pr() 내부 hook 위치는 task-2509 설계 시점부터 5 모듈 wiring을 염두에 두고 박제된 자리이므로 가장 정합도가 높다.

**A-B-C 일관성 검증**: 모두 "새 abstraction 0 + 5 모듈 본체 무수정 + dispatch 무수정"이라는 같은 회장 제약을 준수하며, 기존 hook 박제 자리를 실제 호출로 채우는 단일 결정에 수렴한다. 일관성 PASS.

## 참조 자료

- 정책 본체: `memory/feedback/feedback_critical_escalation_only_260508.md`
- 공통 계약: `utils/automation_contracts.py` (READ ONLY)
- 5 모듈:
  - `utils/merge_queue_executor.py` (wiring 대상)
  - `utils/replacement_pr_runner.py` (READ ONLY)
  - `utils/auto_gemini_triage.py` (READ ONLY)
  - `utils/post_merge_smoke_runner.py` (READ ONLY)
  - `utils/critical_escalation_reporter.py` (READ ONLY)

## 주의사항

- task-2509 CLI 인터페이스 (`--pr`, `--dry-run`, `--task-file`, `--no-dry-run`, `--smoke-command`, `--no-audit`) breaking change 금지 — 기존 인자 모두 보존.
- expected_files 정확히 2 파일 (merge_queue_executor.py, tests/regression/test_orchestration_runtime_2514.py) — Merge Topology Gate 자기참조.
- 모든 critical 7종 라우팅은 LEGACY_CRITICAL_MAP을 통해 기존 코드 (`CRITICAL_FORBIDDEN_PATH` 등 string)와 자동 매핑됨. emit_critical_escalation() 호출부 무수정.
- post_merge_smoke 함수 이름 충돌 — import alias 필수 (`run_pm_smoke_v2` 등).
