[HAN-268] Result — Raphael 발송 Spec Gate auto-approval + 회수 메커니즘
Summary
PR #100 머지 완료 (b84c449, fast-forward). Raphael 페르소나가 발송한 [Spec Gate] 브리핑도 Jarvis 자동 검토→[APPROVED]→라벨/리액션/execution 픽업이 동작하며, 사용자 👎 또는 [CANCEL] 코멘트로 회수 가능한 fail-safe 경로 추가. 13 TC + 전체 122 통과, code review clean pass.
완료된 것
코드 변경
| 파일 | 변경 요약 |
|---|---|
| `bridge.py` | subtype 가드 완화(`bot_message` 허용) · `_handle_approval_briefing` pending.json hydrate · idempotency 가드 · `_handle_cancel_comment` 신규 · 단계별 회수 분류 · Jarvis 자동 승인 시 사용자 DM 알림 |
| `spec_gate.py` | `handle_reaction` thumbsdown 시 `approved`/`implementing` 함께 제거 + 단계별 사용자 DM (label_only / processing / done) |
| `tests/test_approval_handler.py` | 7 신규 TC + `autouse _isolate_env` fixture (env + store 경로 격리) |
| `.agent/docs/HAN-268.md` | SDD 스펙 (spec-reviewer 통과) |
AC 통과
- [x] Raphael 발송 [Spec Gate] 브리핑이 #agent-approvals에 도착하면 Jarvis 자동 검토 실행
- [x] Jarvis [APPROVED] 응답 직후 Linear 티켓에
approved라벨 자동 부여 - [x] 동일 응답에 👍 리액션 자동 추가
- [x] execution_scan_loop이 approved 라벨 픽업해 worktree 생성·SDD 파이프라인 실행
- [x] fail-safe: 사용자 👎 리액션 또는
[CANCEL]코멘트 시 approved 라벨 즉시 제거 + 단계별 회수 - [x] 회수 동작이 Slack DM(Jarvis → 사용자)으로 알림 발송
회수 단계 분류
| Stage | 조건 | 회수 동작 |
|---|---|---|
| `label_only` | execution_store에 처리 흔적 없음 | 라벨만 제거 |
| `processing` | execution_store에 worktree 생성됐으나 PR 없음 | 라벨 제거 (워크트리 강제 종료는 별도 이슈) |
| `done` | execution_store에 `pr_url` 존재 | 회수 불가 + 사용자 DM에 명시 |
배운 것
Plan 단계 — bundle vs split 판단
사용자가 HAN-268·HAN-168 묶음 검토 요청. 표면 가설("비-멤버 봇 멘션 무효")이 유사해 보였으나 코드 surface·트리거 메커니즘·실패 본질이 모두 달랐음:
- HAN-268은 Jarvis가 이미 채널 멤버 → 핸들러 로직 문제
- HAN-168은 Asurada가 DM 비멤버 → Slack IM 2인 멤버십 구조 문제
→ premature abstraction 회피: 표면 유사성에 끌려 묶지 말 것. 3번째 유사 케이스 나오면 그때 추상화 검토.
TDD 사이클 + 회귀 baseline 가치
기존 test_approval_handler.py의 6 TC가 회귀 baseline으로 작동. subtype 가드를 not event.get("subtype")에서 subtype in (None, "bot_message")로 변경할 때 안전망 역할. TC8(test_message_changed_subtype_does_not_call_handler)가 의도치 않은 admission(message edit 이벤트) 방지.
Code review 분류 기준
5-agent code review에서 7건 후보 발견했으나 모두 80점 미만:
- 70점: test isolation (functionality 영향 X)
- 65점: None 가드 누락 (broad try/except로 dampened)
- 60점: per-instance lock race (collision 빈도 moderate)
- 50점 이하: docstring drift, 가설 검증, 테스트 inline-guard drift
→ 80점 임계값이 합리적: 50-70대는 follow-up 후보. 머지 차단할 정도는 아님.
사용자 결정 패턴: "전체 자동화 + fail-safe"
> "전체 자동화. 밀려오는 작업을 다 파악하기 힘들고, 문제 생기면 그때 수정하는 전략."
자동화의 위험을 fail-safe 메커니즘으로 덮는 패턴. 단계별 회수 동작(라벨만 / worktree 진행 중 / PR 생성됨)을 명확히 정의해 사용자가 "어디까지 진행됐는지" 파악 후 결정 가능. silent 진행 알림(자동 승인 시 DM)도 같은 맥락.
코드 리뷰 후보 (follow-up)
머지 차단은 아니지만 향후 검토 가치 있는 항목:
1. _handle_cancel_comment linear_client is None 명시 가드 (HAN-65 패턴, score 65) — broad try/except로 crash는 막지만 사용자 DM "회수됨"이 false success 신호 전달
2. _classify_recovery_stage in spec_gate.py monkeypatch hook 부재 (score 70) — TC12 fixture가 bridge 측만 patch, spec_gate 측은 real default 파일 읽음 → CI flakiness 위험
3. spec_gate.py handle_reaction docstring drift (score 50) — thumbsdown 동작이 auto-discovery 제거만 명시, 실제는 approved/implementing + DM
4. _get_spec_gate_store() 싱글톤화 (score 60) — per-instance threading.Lock이 cross-thread 직렬화 안 됨
별도 follow-up 티켓 필요 시 생성. 본 이슈 자체는 Done.
링크
- **PR**: https://github.com/hangseung/pantheon/pull/100 (merged
b84c449) - **Linear**: https://linear.app/hangman-lab/issue/HAN-268
- **Plan**: https://github.com/hangseung/hangman-docs/blob/main/reports/HAN-268-plan.html
- **Spec**:
.agent/docs/HAN-268.md(main 브랜치) - **분리된 이슈**: HAN-168 (Backlog 복귀, 5월 말~6월 초 픽업 예정)