advisory HAN-322 raphael final 2026-05-23

[자문] Spec Gate 24h 에스컬레이션 흐름 설계

TL;DR

check_timeouts() 단일 함수 확장 + 배치 DM + mark 선행. PR #122 구현 완료.

질문 / 결정 사항

Spec Gate 48h ON_HOLD 전 24h 시점에 Jarvis DM 경보를 보내는 에스컬레이션 흐름 설계. 타이머 등록/취소 메커니즘과 트리거 조건 결정.

전제 (사용자 확인)

옵션 비교

Option A: check_timeouts() 단일 함수 확장

장점 - 신규 데몬 스레드 없음 - 기존 1h 폴링 루프 재사용 - 48h→on-hold 로직과 순서 보장이 자연스럽게 처리됨

단점 - ±1h 폴링 딜레이 (24h~25h 사이 임의 시점)

트레이드오프 - 단순성 우선 / 정밀도 포기


Option B: 별도 check_escalation() + 데몬 스레드

장점 - 관심사 분리 명확

단점 - 데몬 스레드 추가 → 운영 복잡도 증가 - 48h 체크와의 실행 순서 보장 필요 (별도 동기화)

트레이드오프 - 복잡성 증가 대비 실익 미미 (폴링 방식이므로 정밀도 동일)

권장안

선택: Option A

근거: - if elapsed >= 48h ... elif elapsed >= 24h 순서로 54h+ 항목 중복 알림 자동 방지 - 기존 Lock 기반 RMW 패턴 그대로 재사용 (mark_escalated 동일 구조) - 배치 DM: 동일 폴링 사이클 다수 티켓을 단일 메시지로 묶어 DM 폭탄 방지

맹점 / 리스크

DM 발송 실패 시 escalated_at 불일치 - mark_escalated() 선행 → DM 실패 시 재발송 없음 - 반대로 mark 후행(DM 성공 후 mark)이면 중복 DM 가능 - 판단: 중복 DM이 더 나쁜 UX → mark 선행 채택

봇 다운타임 Skip 케이스 - 봇이 23h~49h 동안 중단 후 재시작 시 24h 경보 생략, 곧바로 on-hold - 현재 48h 시스템도 동일 동작 → 기존 설계 방침 그대로 허용

구현 변경 요약

변경 내용
SpecGatePendingStore.write() escalated_at: null 명시 추가
SpecGatePendingStore.mark_escalated() Lock 내 RMW로 escalated_at 기록
_make_deeplink() archives/{channel}/p{ts_nodot} 딥링크 생성
_send_escalation_dm() 배치 DM: 건수 + 딥링크 + 남은 시간
check_timeouts() escalation_hours 파라미터 추가, elif 분기 추가
SPEC_GATE_ESCALATION_HOURS 환경변수 (기본 24)

참고 자료