Drift Breach Notification Dispatch (#1284 G-1 M3)
apps/api/src/gend_api/services/drift_service.py 의 compute_and_persist() 가 drift_status == "breach" 시 자동으로 NotificationService.dispatch() 호출 — Slack/Email/Webhook/GenOS 채널 중 등록된 채널에 알림 발송.
디자인 결정
1. 왜 fire-and-forget?
- DriftReport 저장이 알림 발송 실패에 영향받지 않아야 함
try/except + logger.exception(Audit middleware_fire_and_forget패턴)- 알림 실패 시
drift_report.notified_at = None유지 (재시도 추적용)
2. 왜 NotificationService 추상화 활용?
- provider-agnostic (Slack/Email/Webhook/GenOS/Telegram)
- 환경별 선택 가능 (on-prem 가스공사: Email/Webhook 권장)
- 신규 provider 추가 시 drift_service 코드 변경 0
3. 왜 event_type="drift.breach"?
- NotificationChannel.events 배열로 채널이 구독할 이벤트 필터링
- 워크스페이스 admin 이 "drift 만 받기" / "drift+model.predict.denied 받기" 등 세밀 제어
코드 핵심
async def compute_and_persist(self, ...):
report = await self.compute_drift(reference, current, threshold)
db_row = await self._persist(report)
if report.drift_status == "breach":
try:
event = NotificationEvent(
event_type="drift.breach",
title=f"Drift breach: {model_name}@{model_version}",
message=self._format_message(report),
metadata={
"model_name": model_name,
"drift_score": report.drift_score,
"drifted_features": report.drifted_features[:3],
"report_url": f"/admin/drift/{db_row.id}",
},
)
result = await NotificationService.dispatch(event, self._db)
if any(r.success for r in result):
db_row.notified_at = datetime.utcnow()
await self._db.commit()
except Exception:
logger.exception("drift breach notification dispatch failed (silent)")
return db_row
환경별 권장 Provider
| 환경 | Provider | webhook URL/SMTP 출처 |
|---|---|---|
| GenD prod (Azure) | Slack | Slack workspace admin 발급 webhook |
| on-prem 가스공사 | 사내 SMTP (smtp.kogas.co.kr) | |
| on-prem 일반 | Webhook | Mattermost/Rocket.Chat hooks |
| 폐쇄망 | GenOS | GenOS user_group 내부 알림 |
자세한 가이드: notification-provider-guide
회귀 가드 — 7 tests
apps/api/tests/test_drift_breach_notification.py:
- breach → dispatch 호출 + event_type="drift.breach"
- ok / warn → dispatch 호출 X
- dispatch 실패 → drift_report 정상 저장 (notified_at=None)
- dispatch 성공 → notified_at 갱신
- empty drifted_features 안전 (KeyError X)
- message body 에 features 상위 3개 표기
트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| breach 인데 알림 안 옴 | NotificationChannel 미등록 | POST /api/v1/notification/channels |
| Webhook 401 | webhook URL 만료/회전 | Slack workspace re-issue |
| notified_at 항상 None | NotificationService.dispatch silent fail | gend-api log gend.audit 확인 |
| 메시지에 features 누락 | DriftReport.drifted_features 빈 list | DriftService.compute_drift threshold 조정 |
관련
- PR #1284
- E2E 검증: notification-channel-e2e-verify
- Provider 가이드: notification-provider-guide