# task-2367 — Tiered Auto-Merge + Audit Log + Health Probe + Auto Revert

## SCQA

### S — Situation (배경)
**S**: P0 (task-2364)이 `task-scope-guard.sh` + capability snapshot으로 "위반 차단"을 구축했지만, 정상 흐름까지 모두 인간 게이트로 막으면 처리량 0. 미팅 합의 2번(Tiered Auto-Merge) + 3번(Post-Merge Safety Net)을 P1로 구현해야 한다.

### C — Complication (장애 요소)
**C**:
- **자기-바이패스 위험**: 본 task 자체가 `auto_merge.py`를 수정 → Tier 3로 분류되어야 함
- **Codex G1 critical 3건**: `git reset --hard` 사용 위험, Tier 3 머지 사후검증 위험, CANCELLED 가드 부족
- **회귀 위험**: P0 산출물(scope-guard) 절대 무영향 + 기존 auto_merge 흐름 보존
- **분산 안전망**: probe → revert → circuit breaker 3단 회복 체계 통합

### Q — Question (해결할 문제)
**Q**:
1. `classify_tier()` 정의/구현 위치는?
2. Audit Log를 mutable JSON 배열에서 append-only JSONL로 어떻게 안전 전환?
3. probe 5분 지연을 어떻게 finish-task.sh에 트리거하면서 멱등성 유지?
4. `git revert -m 1` 기반 PR 자동 생성 워크플로우?
5. Circuit Breaker 누적 카운팅 + 24h cooldown?
6. 본 task가 Tier 3로 정상 분류되는지 자가 검증?

### A — Answer (해결책)
**A**: P1을 4 Phase로 분해, 쿠쿨칸/카마소츠에 위임:
1. **Phase 1 (auto_merge.py 확장)** — `classify_tier` + audit JSONL + `git revert -m 1` 안전화
2. **Phase 2 (신규 모듈)** — `post_merge_probe.py` + `auto_revert.py` + dispatch.py circuit breaker
3. **Phase 3 (finish-task.sh)** — `.merge-done` 직후 5분 background probe 트리거
4. **Phase 4 (테스트)** — tier/probe/revert/circuit breaker + 자기-바이패스 검증 18종 + 회귀 12종

---

## 작업 내용

### 신규 파일
| 파일 | 역할 | 라인 |
|---|---|---|
| `scripts/post_merge_probe.py` | 머지 후 5분 health probe (build/test 재검증) | 178 |
| `scripts/auto_revert.py` | probe FAIL 시 `git revert -m 1` PR 생성 | 133 |
| `memory/audit/.gitkeep` | audit 디렉토리 마커 | - |
| `memory/audit/violation-count.json` | Circuit Breaker 봇별 누적 (`{}` 초기) | 1 |
| `tests/dev7/test_tier_classifier.py` | Tier 분류 + audit log 9 시나리오 | 175 |
| `tests/dev7/test_post_merge_probe.py` | probe pass/fail/idempotent 3 시나리오 | 86 |
| `tests/dev7/test_auto_revert.py` | git revert -m 1 검증 + audit reason 2 시나리오 | 101 |
| `tests/dev7/test_circuit_breaker.py` | 누적/차단/독립/리셋 4 시나리오 | 68 |

### 수정 파일
- `scripts/auto_merge.py` (+338 -17): `classify_tier`, `_load_capability_snapshot`, `_has_protected_path`, `_diff_loc`, `_has_dependency_change`, `_load_task_level`, `_append_audit` 추가. `revert_merge`를 `git revert -m 1` 기반으로 안전화 (★ `git reset --hard` 완전 제거). `run()` 안 tier 분기 삽입.
- `dispatch.py` (+99): `_check_circuit_breaker`, `_record_violation` 추가. 단일 진입점(line 3163, team_id 확정 후 routing 검증 이전)에 호출 삽입.
- `scripts/finish-task.sh` (+20): `.merge-done` 직후 5분 후 `post_merge_probe.py` background 실행 + 멱등 마크 검사.

### 수정 파일별 검증 상태

| 파일 | grep 검증 키워드 | 결과 |
|---|---|---|
| `scripts/auto_merge.py` | `classify_tier`, `_append_audit`, `_load_capability_snapshot` | ✅ line 967, 1006, 846 발견 |
| `scripts/post_merge_probe.py` | `def run_probe`, `PROBE_MARK_DIR` | ✅ 발견 + L1 smoke PASS |
| `scripts/auto_revert.py` | `def create_revert_pr`, `revert.*-m` | ✅ 발견, `reset --hard` 0건 |
| `dispatch.py` | `_check_circuit_breaker`, `_record_violation` | ✅ line 2292, 2339 발견 |
| `scripts/finish-task.sh` | `post_merge_probe.py`, `POST-PROBE` | ✅ line 440, 446 발견 |
| `tests/dev7/test_*.py` | pytest 4개 모듈 | ✅ 18 PASS (회귀 12 무영향) |

### 머지 sha 8개 (worktree 브랜치)
```
814c8b84 post_merge_probe PROBE_MARK_DIR 자동 생성
dbe63084 테스트 type 안전성 보강
c5e4a254 Phase 4 — 4개 테스트 모듈
b3037a09 Phase 3 — finish-task.sh probe 트리거
4604a776 unused sys import 제거
a986bbd5 Phase 2 — probe + revert + circuit_breaker
5a1a48d4 unused hashlib import 제거
14bea253 Phase 1 — classify_tier + audit log + revert 안전화
```

---

## 6 검증 시나리오 매트릭스

| # | 시나리오 | 기대 결과 | 실제 결과 | 검증 |
|---|---|---|---|---|
| 1 | Tier 1 자동 머지 (Lv.0-1 + 작은 diff + 보호 경로 없음) | tier1 반환, 기존 머지 흐름 진입, audit `outcome=merged` | ✅ PASS | `test_tier1_lv0_small_diff` |
| 2 | Tier 2 큐 (Lv.2 / 다중 파일 / 의존성 변경) | tier2 반환, escalate(`Tier 2 — 1-tap approval queue`), audit `outcome=escalated` | ✅ PASS | `test_tier2_lv2`, `test_tier2_multifile`, `test_tier2_dependency_change` |
| 3 | Tier 3 차단 (Lv.3+ / 보호 경로 / 글로벌 보호) | tier3 반환, escalate, **자동 머지 X** | ✅ PASS | `test_tier3_lv3`, `test_tier3_protected_path`, `test_tier3_global_protected` |
| 4 | Health Probe FAIL → Auto Revert | probe build FAIL → `auto_revert.py` Popen 트리거, `git revert -m 1` 호출 (★ `reset --hard` 0건) | ✅ PASS | `test_probe_fail_triggers_revert`, `test_revert_uses_git_revert_not_reset` |
| 5 | Circuit Breaker (봇별 누적 3회 → 차단) | 3회 누적 후 `allowed=False`, 24h cooldown 자동 설정, 봇별 독립 카운트 | ✅ PASS | `test_three_violations_blocks`, `test_independent_bots`, `test_record_violation_persists` |
| 6 | 회귀 (P0 task-2364 산출물 무영향) | `test_scope_guard.py` 12개 모두 PASS | ✅ PASS | 12/12 PASS |

**자기-바이패스 방어 (★)**:
- `test_self_bypass_prevention` — 임시 환경 PASS
- 실제 capability snapshot으로 검증: `python3 classify_tier('task-2367', diff_files)` → **Tier 3** ✅

---

## L1 스모크테스트 결과

- **서버 재시작**: 해당없음 (백엔드 라이브러리 작업, 데몬 없음)
- **API 응답 확인**: 해당없음 (API 모듈 변경 없음)
- **실제 실행 검증**:
  - `post_merge_probe.py` 직접 실행 (delay=0, 빈 프로젝트):
    ```json
    {
      "outcome": "probe_pass",
      "merger": "post_merge_probe.py",
      "probe": {"build_ok": true, "test_ok": true},
      "sequence": 1
    }
    ```
  - audit log JSONL append-only 동작 확인 (sequence 단조 증가)
  - 본 task의 capability snapshot으로 `classify_tier` 호출 → `tier3` 반환 (자기-바이패스 방어)
- **스크린샷**: 해당없음 (UI 작업 없음)

---

## 테스트 결과

```
tests/dev7/test_auto_revert.py ..                                        [  6%]
tests/dev7/test_circuit_breaker.py ....                                  [ 20%]
tests/dev7/test_post_merge_probe.py ...                                  [ 30%]
tests/dev7/test_scope_guard.py ............                              [ 70%]  ← P0 회귀 0
tests/dev7/test_tier_classifier.py .........                             [100%]

============================== 30 passed in 0.26s ==============================
```

- pytest 30/30 PASS (P1 18 + P0 회귀 12)
- `python3 -m py_compile scripts/auto_merge.py scripts/post_merge_probe.py scripts/auto_revert.py dispatch.py` → exit 0
- `bash -n scripts/finish-task.sh` → PASS

---

## Codex G1 사전 검증 대응

Codex가 critical 3건 + high 4건 + medium 2건 지적. high/medium 6건은 본 task의 **구현 대상 자체**(미구현 상태 진단). critical 3건을 설계에 반영:

| Codex 지적 | 반영 |
|---|---|
| `git reset --hard HEAD~1` 위험 (critical) | `revert_merge`를 `git revert -m 1`로 교체. `auto_revert.py`도 동일 패턴. `grep -n "reset.*hard"` → 0건 (주석 제외). |
| Tier 3 사후검증 위험 (critical) | `classify_tier()`가 `run()` 안 머지 진입 **전** 호출. Tier 3 → 즉시 escalate, 머지 X. |
| CANCELLED 가드 부족 (critical) | 기존 finish-task.sh CANCELLED 가드 유지. `post_merge_probe.py`는 `.probe-done` 멱등 마크로 재실행 차단. (dispatch.py 추가 가드는 회귀 위험으로 본 task 범위 외) |

---

## 발견 이슈 및 해결

### 이슈 1 — `post_merge_probe.py` PROBE_MARK_DIR 미생성
- **증상**: L1 smoke에서 임시 workspace 환경에서 `FileNotFoundError`
- **원인**: `mark_file.write_text()` 직전 디렉토리 보장 누락
- **해결**: `run_probe()` 진입 시 `PROBE_MARK_DIR.mkdir(parents=True, exist_ok=True)` 추가
- **확인**: L1 smoke 재실행 → 정상 `probe_pass` outcome

### 이슈 2 — Pyright unused import / dynamic import 경고
- **증상**: `hashlib`, `sys`, `os` unused / `importlib.util.spec_from_file_location` 반환 type 경고
- **해결**:
  - unused import 제거 (별도 commit)
  - dynamic import에 `assert spec is not None and spec.loader is not None` 추가
  - 테스트 파일 상단 `# pyright: reportAttributeAccessIssue=false` (모듈 attribute 동적 할당 허용)

---

## 머지 판단

- **머지 필요**: **Yes (★ Tier 3 — 회장 명시 승인 필수)**
- **브랜치**: `task/task-2367-dev7`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2367-dev7`
- **머지 의견**:
  - ✅ 30/30 테스트 PASS, 회귀 0건
  - ✅ 본 task 자체가 Tier 3로 자가 분류 → self-bypass 방지 동작 검증
  - ✅ Codex critical 3건 모두 설계에 반영
  - ⚠️ **본 PR은 자동 머지 불가** — 본 task가 정의한 Tier 3 정책상 회장 명시 승인 필수
  - 권장 액션: PR 생성 + 회장 GitHub UI 1클릭 머지 (P0 PR #5와 동일 패턴)

---

## 모델 사용 기록

- **이참나(팀장, Opus)**: 설계, 위임, 통합, 보고서 — 코딩 직접 수행 0
- **쿠쿨칸(백엔드, Sonnet)**: Phase 1/2/3 구현 (auto_merge.py, post_merge_probe.py, auto_revert.py, dispatch.py, finish-task.sh)
- **카마소츠(테스터, Sonnet)**: Phase 4 테스트 4개 모듈 + 회귀 검증
- **이쉬첼/아쿠인 비활성**: 본 task는 100% 백엔드 인프라
- **haiku 사용 0건**: 모든 위임이 인프라 핵심 모듈이라 sonnet 이상 필수

---

## 후속 권장사항

| Phase | 위임 | 제안 |
|---|---|---|
| **P2 (task-2368)** | dev7 또는 dev1 | Telegram inline button webhook + Daily Digest (Tier 2 큐 처리) |
| **P3 (task-2369)** | dev1 (헤르메스) | Dashboard 권한·사고 시각화 (audit log 시각화) |
| **운영** | 아누 | Tier 3 PR 큐 모니터링 cron 추가 (회장 명시 승인 대기 체크) |
| **회귀 모니터** | dev6 또는 dev7 | 본 PR 머지 후 첫 5건 자동 머지 시 audit log 수동 검수 (10% sampling — 마아트 합의안) |

---

## 참조

- task 명세: `memory/tasks/task-2367.md`
- 미팅 기록: `memory/meetings/2026-05-02-bot-anu-automation-safety.md`
- 3문서: `memory/plans/tasks/task-2367/{plan,context-notes,checklist}.md`
- P0 보고서: `memory/reports/task-2364.md`, PR #5 (`05755351`)
- capability snapshot: `memory/capabilities/task-2367.json` (sha=`7ae808529969`)
- audit log: `memory/audit/auto-merge.log` (JSONL append-only)
- Codex G1 결과: `memory/events/task-2367.codex-gate`

---

## 8 commits

```
814c8b84 [task-2367] 쿠쿨칸: post_merge_probe.py — PROBE_MARK_DIR 자동 생성 (L1 smoke 통과)
dbe63084 [task-2367] 카마소츠: 테스트 type 안전성 보강 (assert spec, pyright suppress)
c5e4a254 [task-2367] 카마소츠: Phase 4 — tier classifier + probe + revert + circuit breaker 테스트
b3037a09 [task-2367] 쿠쿨칸: Phase 3 — finish-task.sh post-merge probe 5분 background 트리거
4604a776 [task-2367] 쿠쿨칸: post_merge_probe.py unused sys import 제거
a986bbd5 [task-2367] 쿠쿨칸: Phase 2 — post_merge_probe + auto_revert + circuit_breaker
5a1a48d4 [task-2367] 쿠쿨칸: hashlib unused import 제거
14bea253 [task-2367] 쿠쿨칸: Phase 1 — classify_tier + audit log + revert_merge 안전화
```

★ 본 PR은 Tier 3 자기-분류로 자동 머지 차단됨. 회장 명시 승인 시 GitHub UI 1클릭 머지 필요.
