---
task_id: task-2539
type: context
scope: task
created: 2026-05-10
updated: 2026-05-10
status: in-progress
---

# 맥락 노트: task-2539

**task**: task-2539

---

## 사고 배경

- **task-2537 POST_MERGE_AUDIT_WARN 사례**: qc-result `l1_smoketest_check=PASS` 단일 신호만 존재했고 독립 `.smoke-evidence` marker가 없어서 WARN 처리됨. 기존 산출물 evidence-backfill로 PASS 정정.
- **task-2524 박제 첫 사례 (dev5 마르둑, 2026-05-10 21:32)**: `task-2524.smoke-evidence` + `task-2524.reconcile-evidence` jsonl 박제 첫 사례.
- **본 task-2539**: task-2524 사례를 모든 task 공통 자동화로 정식 spec 박제.

## 회장 §명시 절대 원칙

- md/report 문구 fallback 절대 금지
- 실제 runner output / marker / exit code 기준만 판정
- smoke exit_code != 0 → Critical 7종 #7 (POST_MERGE_SMOKE_FAILURE) 분류
- one-way isolation: anu_v2 외부 import 금지
- token raw 0 / chat_id != 6937032012 record 작성 금지

## 3 Step Why 자문

- **1st Why: 왜 이 설계(독립 .smoke-evidence marker)가 필요한가?**
  → A: qc-result md 본문에 "smoke PASS" 텍스트가 포함되어도 그것은 보고서 작성자의 주관 신호일 뿐 실제 runner 실행 증거가 아님. 독립 jsonl marker만이 실제 실행+exit_code 0 사실을 박제할 수 있음. task-2537 WARN 사례가 이를 실증.

- **2nd Why: 왜 A(독립 marker 박제)가 최선의 접근인가?**
  → B: 대안1 (qc-result 필드 신뢰) — 작성자 의도 오염 위험. 대안2 (GitHub Actions log 보관) — 외부 의존성 + 토큰 노출 위험. 본 설계 (로컬 jsonl + chat 격리 + token sanitize) — 외부 의존 0, 검증 시점 = 머지 직후 즉시, 회귀 fixture로 검증 가능. B가 검증 비용 + 의존성 + 박제 강도 모두에서 최선.

- **3rd Why: 왜 B(jsonl 독립 marker)가 다른 대안보다 나은가?**
  → C: jsonl append-only 형식은 (1) idempotent — 동일 task 2회 실행해도 history 보존, (2) chat_id 어설션과 결합 시 cross-chat 누출 차단, (3) task-2524 박제 형식과 1:1 호환되어 기존 자료 재처리 불필요, (4) Critical 7종 #7 분류와 직결되어 회장 보고 chain이 명확. md/JSON 단일 객체 형식 대비 모든 면에서 우수.

A-B-C 논리 일관: ✅ — 독립 박제 필요성 → 외부 의존 0 + 회귀 검증 가능 → jsonl append-only가 회귀/격리/호환 모두 충족.

## 모듈 구조 결정

- **클래스**: `PostMergeSmokeRunner` (단일 클래스)
- **상수**: `DEFAULT_SMOKE_PROFILE`, `SMOKE_TIMEOUT_SECONDS`, `DEFAULT_CHAT_ID`, `CRITICAL_SEVEN_KIND_POST_MERGE_SMOKE_FAILURE`, `TOKEN_KEY_HINTS`(모듈 내부 재정의 — auto_gemini_triage import 금지)
- **method 6개**: run_post_merge_smoke / _resolve_smoke_command / _execute_smoke / _build_smoke_evidence / _write_smoke_evidence_marker / classify_smoke_failure_as_critical_seven
- **외부 부수효과 추상화**: subprocess.run / file write / time / capabilities loader를 주입 가능한 callable로 노출 — 회귀 테스트가 fake로 검증

## 회귀 9건 매핑

1. PASS marker — fixture task-2524 + fake subprocess(exit 0) → run_post_merge_smoke → marker 생성 + format 일치
2. FAIL Critical 7 — fake subprocess(exit 1) → critical_seven_classification=7 / kind_name=POST_MERGE_SMOKE_FAILURE / marker 미생성
3. command 우선순위 — 4 케이스: 명시 > task config(capabilities/<task_id>.json::smoke_command) > smoke_profile > DEFAULT
4. idempotent — 동일 task 2회 호출 시 marker append만 (line 2개, 덮어쓰기 X)
5. chat 격리 — chat_id != 6937032012 → AssertionError
6. token raw 0 — stderr에 "GH_TOKEN=ghp_xxx" 시뮬레이션 → marker / Critical 7 본문에 raw token 0 (sanitize)
7. md/report fallback 금지 — qc-result md "smoke PASS" 텍스트 + 실제 smoke FAIL → outcome=FAIL (md 무시). marker 디렉토리 write 실패 시 EVIDENCE_INCOMPLETE
8. timeout — TimeoutExpired raise → outcome=FAIL / Critical 7 / exit_code 124 (또는 비-0)
9. interface contract — inspect.signature로 method 시그니처 보존 검증

## 참조 자료

- task 명세: `/home/jay/workspace/memory/tasks/task-2539.md`
- 기존 anu_v2 패턴: `/home/jay/workspace/anu_v2/auto_gemini_triage.py`
- task-2524 박제 marker (참조): `/home/jay/workspace/memory/events/task-2524.smoke-evidence`
- smoke baseline: `/home/jay/workspace/tests/smoke/test_smoke_baseline.py`

## 주의사항

- forbidden_paths: `utils/`, `dispatch/`, `scripts/`, `dashboard/`, `teams/`, `memory/tasks/**`, 다른 anu_v2 모듈 (replacement_pr_runner / merge_queue_executor / auto_gemini_triage / pr_open_gemini_trigger_prevention / critical_escalation_reporter)
- expected_files 정확 4개: post_merge_smoke_runner.py / test_post_merge_smoke_runner_2539.py / fixtures 2개
- 외부 AI(Codex/Gemini) 호출 시 sanitize 게이트 통과 필수 — 본 모듈은 PII 가능성 낮으나, fixture에 token이 들어가면 안 됨
- backfill은 본 모듈 책임 아님 (read-only audit checker가 별도 결정)
- finalize 14단계 자체는 본 task에서 머지하지 않음 — 시스템 작업으로 worktree 사용 안 함
