# task-2512 — post_merge_smoke_runner: main 기준 smoke 자동 실행

- 작업 유형: **자동화 코드 구현 + 회귀 테스트** (정책 문서 X)
- 작업 레벨: **Lv.3**
- 우선순위: **★★**
- Track: **post_merge_smoke / smoke_runner / 5 모듈 #4**
- parallel_policy: **limited_parallel** (회장 명시 — task-2513과 병렬 가능)
- 일시: 2026-05-09
- 회장 결정: 2026-05-09 직접 발행 (5 모듈 #4)

## ⚠️ 본 task의 본질 — 회장 명시

> 자동 머지 직후 origin/main 기준 smoke를 자동 실행하고,
> smoke 실패 시에만 Critical escalation (#7 POST_MERGE_SMOKE_FAILED)로 보고한다.
> 산출물 = 코드 + 회귀 테스트. 문서는 부속.

## dependency (모두 충족)

- ✅ task-2509.merged (PR #58)
- ✅ task-2509+2.merged (PR #60, automation_contracts.py freeze — SmokeResult/EscalationPacket 사용)
- ✅ task-2510.merged (PR #61, replacement_pr_runner)
- ✅ task-2511.merged (PR #62, auto_gemini_triage)

## 병렬 정책 (회장 명시)

- **task-2513과 병렬 가능** (limited_parallel)
- 두 task expected_files가 disjoint (utils/post_merge_smoke_runner.py vs utils/critical_escalation_reporter.py)
- 단, **task-2514 wiring은 두 task 모두 main 머지 완료 전까지 발사 금지**
- task-2514는 마지막 serial orchestration runtime wiring 단계

## Merge Topology Gate metadata

```yaml
expected_files:
  - "utils/post_merge_smoke_runner.py"
  - "tests/regression/test_post_merge_smoke_runner_2512.py"

risk_area: "post_merge_smoke / smoke_runner / critical_7_smoke_failure"

dependency:
  - "task-2509.merged"
  - "task-2509+2.merged"
  - "task-2510.merged"
  - "task-2511.merged"

parallel_policy: "limited_parallel"

merge_queue_position: 9

stale_recheck_required: true

cherry_pick_allowed: false
```

## 실전 replay fixture (회장 명시 — 반드시 replay)

- **task-2506 smoke**: PR #56 머지 후 main 기준 smoke
- **task-2507 smoke**: PR #55 머지 후 main 기준 smoke (10/10 PASS 사례)
- **task-2509 smoke**: PR #58 머지 후 main 기준 smoke
- **task-2511 smoke**: PR #62 머지 후 main 기준 smoke (방금 완료된 사례)

본 fixture를 회귀 테스트 입력으로 사용 — 동일 결과 재현 필수.

## 필수 구현 10건 (회장 §1~10)

### 1. merge_commit 기준 main smoke 실행
- 입력: `merge_commit` SHA
- 동작: `git fetch origin` + main worktree fast-forward → smoke command 실행
- merge_commit이 origin/main HEAD와 일치하지 않으면 stale 판정

### 2. smoke_command registry 처리
- task spec에서 smoke command 추출 (회장 §자동 머지 10조건 #10 "post-merge smoke 정의됨")
- registry: task_id → smoke_command 매핑
- 미정의 시 정책 #10 적용

### 3. subprocess timeout 처리
- 기본 timeout 600초 (configurable)
- timeout 발생 시 SmokeResult.status = TIMEOUT
- subprocess.kill + zombie 방지

### 4. stdout/stderr capture
- 두 stream 분리 capture
- size cap (기본 64KB) 초과 시 head/tail 보존

### 5. PASS / FAIL / SKIPPED / TIMEOUT 분류
- PASS: returncode 0 + 정상 종료
- FAIL: returncode != 0
- SKIPPED: smoke_command=None + dry_run=True
- TIMEOUT: subprocess.TimeoutExpired

### 6. SmokeResult 반환 (automation_contracts.py freeze)
- import: `from utils.automation_contracts import SmokeResult, CriticalEscalationType, EscalationPacket`
- `SmokeResult(merge_commit / status / stdout / stderr / duration_ms / smoke_command / task_id)`

### 7. smoke 실패 시 Critical #7 escalation packet 생성
- `CriticalEscalationType.POST_MERGE_SMOKE_FAILED`
- `EscalationPacket(escalation_type / merge_commit / task_id / evidence)`

### 8. smoke PASS 시 merge_queue continuation 가능 상태 반환
- `AutomationDecision(allow_continuation=True)` 또는 `merge_queue_continuation_ready=True` field
- task-2514 wiring 단계에서 merge_queue_executor가 이 신호로 다음 queue 처리

### 9. replay fixture (회장 §9)
- task-2506 / task-2507 / task-2509 / task-2511 smoke 4개 fixture를 회귀 입력으로 사용

### 10. smoke 미정의 시 정책 (회장 §10)

| dry_run | smoke_command | 결과 |
|---|---|---|
| True | None | **SKIPPED** 허용 |
| True | defined | smoke 실행 |
| False | None | **escalation 또는 BLOCKED** (자동 머지 거부, task-2509+1 §5 정합) |
| False | defined | smoke 실행 + PASS 필수 |

## 필수 회귀 테스트 12건 (회장 §1~12)

`tests/regression/test_post_merge_smoke_runner_2512.py`:

1. PASS smoke (returncode 0 → SmokeResult.status=PASS)
2. FAIL smoke (returncode != 0 → SmokeResult.status=FAIL + Critical #7 packet)
3. timeout smoke (subprocess.TimeoutExpired → status=TIMEOUT + Critical #7 packet)
4. missing smoke command (None) + dry_run=True → SKIPPED
5. missing smoke command + dry_run=False → BLOCKED 또는 escalation
6. stdout capture (head/tail 검증)
7. stderr capture (size cap 검증)
8. JSON serialization (SmokeResult round-trip)
9. Critical #7 packet 생성 (POST_MERGE_SMOKE_FAILED enum 정확 매칭)
10. merge_commit propagation (입력 SHA가 SmokeResult/EscalationPacket에 그대로 기록)
11. **★ replay smoke fixtures**: task-2506/2507/2509/2511 4개 fixture 동일 결과 재현
12. merge_queue continuation 신호 (PASS 시 AutomationDecision.allow_continuation=True)

## CLI entrypoint

`python3 utils/post_merge_smoke_runner.py --task-file <path> --merge-commit <sha> --dry-run`
- 출력: `SmokeResult` JSON
- `--apply` 옵션: 실제 subprocess smoke 실행
- `--no-audit` 옵션 (테스트용)

## 금지 행위 (회장 명시 — 절대 준수)

- **merge_queue_executor wiring 금지** (import 가능성만 확인)
- **dispatch.py 수정 금지**
- **admin override 금지** (`gh pr merge --admin`)
- **force/rebase/manual .done 금지**
- **task-2513 영역 침범 금지** (critical_escalation_reporter 구현 X — Critical #7 packet 생성만)
- **replacement_pr_runner / auto_gemini_triage 수정 금지**
- **PR #52/#49/#50/#51 수정 금지**
- **expected_files 외 수정 금지**
- **자동 cherry-pick 구현 금지**
- **정책 md만 작성하고 종료 금지**
- **Critical 7종 외 회장 보고 금지**
- **amendment 무시 / mid-dispatch correction 무시 금지**

## allowed_resources

```yaml
allowed_resources:
  read_only_paths:
    - "memory/tasks/task-2509*"
    - "memory/tasks/task-2510*"
    - "memory/tasks/task-2511*"
    - "memory/tasks/task-2512*"
    - "memory/feedback/feedback_critical_escalation_only_260508.md"
    - "utils/automation_contracts.py"  # task-2509+2 freeze
    - "utils/merge_queue_executor.py"
    - "utils/replacement_pr_runner.py"
    - "utils/auto_gemini_triage.py"
    - "memory/orchestration-audit/merge-queue.jsonl"
    - "memory/task-timers.json"
    - ".env.keys"
  paths:
    - "memory/tasks/task-2512*"
    - "memory/reports/task-2512*"
    - "memory/events/task-2512*"
    - "utils/post_merge_smoke_runner.py"
    - "tests/regression/test_post_merge_smoke_runner_2512.py"
  forbidden_actions:
    - "merge_queue_executor wiring"
    - "dispatch.py 수정"
    - "critical_escalation_reporter 구현 (task-2513)"
    - "replacement_pr_runner 수정"
    - "auto_gemini_triage 수정"
    - "force push"
    - "rebase"
    - "admin override (gh pr merge --admin)"
    - "manual .done 생성"
    - "required CI bypass"
    - "PR #52/#49/#50/#51 수정"
    - "expected_files 외 수정"
    - "자동 cherry-pick 구현"
    - "정책 md만 작성하고 종료"
    - "Critical 7종 외 회장 보고"
    - "amendment 무시 / mid-dispatch correction 무시"
```

## 완료 조건 (회장 명시)

1. ✅ 실행 가능한 Python 코드 (`utils/post_merge_smoke_runner.py`)
2. ✅ smoke replay PASS (4 fixture 동일 결과 재현)
3. ✅ failure escalation 동작 (Critical #7 POST_MERGE_SMOKE_FAILED packet 생성)
4. ✅ Critical 7종 외 회장 보고 0건
5. ✅ Merge Topology Gate 자기참조 PASS (effective diff = 정확히 2 파일)
6. ✅ CI 11/11 SUCCESS
7. ✅ forbidden path 0
8. ✅ amendment 보호 의무 명시 + 적용 evidence

## 시스템 3문서 참조

- 정책 본체: `memory/feedback/feedback_critical_escalation_only_260508.md`
- 공통 계약: `utils/automation_contracts.py` (task-2509+2 freeze, SmokeResult/EscalationPacket 사용)
- 실전 fixture: task-2506/2507/2509/2511 smoke 사례

## 후행 task

본 task가 5 모듈 #4. **task-2513과 병렬**. 두 task 모두 main 머지 완료 후:
- task-2514 — 5 모듈 wiring (★ 마지막 serial orchestration runtime wiring)

## affected_files (auto-detected)
- utils/post_merge_smoke_runner.py (NEW)
- tests/regression/test_post_merge_smoke_runner_2512.py (NEW)

## goal_assertions (auto-generated)
- `python3 -c "import sys; sys.path.insert(0, '/home/jay/workspace/.worktrees/task-2512-dev3'); from utils.post_merge_smoke_runner import run_post_merge_smoke, SmokeStatus, build_smoke_failed_packet, REPLAY_FIXTURES; print('OK')"`
- `cd /home/jay/workspace/.worktrees/task-2512-dev3 && PYTHONPATH=. pytest tests/regression/test_post_merge_smoke_runner_2512.py -q`