Recovery Overview¶
Recovery 는 Umbra 의 핵심 Core 도메인. 길드 상태를 실시간 미러링하고, 스냅샷으로 보존하며, 사고 시 복구한다. 네 개의 sub-context 로 구성된다: Sync, Snapshot, Restore, AntiNuke. 이 문서는 Recovery 전체 구조를 설명한다.
Bounded context¶
- Type — Core (full Hexagonal)
- Sibling contexts — Licensing (기능 게이팅), Guild (복구 대상), Member (복원 대상)
- Location in codebase —
engine/recovery/
Why this domain exists¶
Umbra 의 제품 정체성은 "Discord 길드의 보호자" 이며, 세 기둥(Protect / Preserve / Restore) 이 이 도메인에 집중된다. 다른 도메인은 사용자/결제/알림 같은 supporting 기능 이지만, Recovery 는 Umbra 가 실제로 해결하는 문제 자체다.
Recovery 가 단일 도메인으로 묶인 이유는 네 기능(Sync, Snapshot, Restore, AntiNuke) 이 같은 데이터 모델 을 공유하기 때문이다.
- Sync 가 DB 에 반영한 길드 상태
- Snapshot 이 그 상태를 JSONB 로 동결
- Restore 가 Snapshot 으로부터 Discord 에 복원
- AntiNuke 가 이상 감지 시 Snapshot + Restore 트리거
네 기능이 다른 도메인이면 경계를 넘는 호출이 빈번해지고 데이터 모델이 흩어진다. 같은 도메인 안에서 sub-context 로 분리 하여 응집도와 경계를 균형잡는다.
Sub-contexts¶
Recovery 내부는 4개 sub-context 로 구성된다.
engine/recovery/subdomain/sync/¶
Discord Gateway 이벤트를 실시간 수집하고 배치로 DB 에 반영. "길드 현재 상태" 의 source of truth 를 유지한다.
- 이벤트 버퍼링 (Redis) → 5초 배치 → DB UPSERT
- 중복 이벤트 merge (같은 aggregate 에 대한 연속 변경)
- Restore 실행 중 pause/resume
- asynq cron 워커
상세: domain/recovery/sync.md
engine/recovery/subdomain/snapshot/¶
길드 상태를 특정 시점 스냅샷으로 저장. 수동 / 스케줄 / AntiNuke / Pre-restore 4가지 경로로 생성.
- PostgreSQL JSONB 컬럼에 단일 문서로 저장
- Plan 별 retention (Pro 7일, Enterprise 30일)
- 트랜잭션 원자성 (부분 저장 불가)
- 스냅샷 크기 한도 5MB
상세: domain/recovery/snapshot.md
engine/recovery/subdomain/restore/¶
스냅샷을 기준으로 Discord 길드 상태를 되돌림. Temporal Workflow 로 실행.
- Sync mode (Git reset 스타일) — 스냅샷에 없는 것 삭제
- 카테고리별 선택 (Roles / Channels / Permission Overrides / Guild Settings)
- diff 미리보기 → 사용자 확인 → 실행
- Temporal Activity retry + Discord rate limit 백오프
상세: domain/recovery/restore.md
engine/recovery/subdomain/antinuke/¶
비정상 이벤트 패턴 감지 + 자동 대응. Temporal Workflow 로 장기 실행.
- 4가지 감지 패턴 (mass role/channel delete, mass kick, permission escalation)
- 감지 시 자동 Snapshot 생성
- Owner DM + 감사 채널 알림
- Enterprise: 가해자 권한 자동 박탈 (opt-in)
상세: domain/recovery/antinuke.md
Sub-context interaction¶
네 sub-context 는 서로 호출하거나 Temporal Signal 로 조정한다. Outbox 는 Recovery 도메인 외부 로 나갈 때만 사용.
graph TB
Sync[sync
Live Sync 배치]
Snapshot[snapshot
스냅샷 생성/조회]
Restore[restore
복구 실행 workflow]
AntiNuke[antinuke
이상 감지 workflow]
Sync -->|writes| DB[(길드 상태)]
Snapshot -->|reads| DB
Snapshot -->|writes| SnapshotStore[(스냅샷)]
Restore -->|reads| SnapshotStore
Restore -->|signal pause/resume| Sync
Restore -->|triggers| Snapshot
AntiNuke -->|signal trigger| Snapshot
AntiNuke -->|signal trigger| Restore
AntiNuke -->|event source| DiscordEvents([Discord Gateway])
Sync -->|event source| DiscordEvents
주요 조정 포인트:
- Sync ↔ Restore — Restore 시작 시 Sync pause, 완료 시 buffer clear + resume
- AntiNuke → Snapshot — 감지 시 긴급 스냅샷 생성 트리거
- AntiNuke → Restore — (옵션) 감지 직후 자동 복구 (Enterprise)
- Restore → Snapshot — pre-restore 스냅샷 자동 생성
Domain model (shared)¶
네 sub-context 가 공유하는 주요 entity:
GuildState (sync 소유)¶
길드의 현재 상태 (역할, 채널, 권한 오버라이드, 길드 설정). recovery.guild_state_* 테이블.
Snapshot (snapshot 소유)¶
특정 시점의 GuildState 동결본. JSONB payload.
RestoreJob (restore 소유)¶
복구 실행 단위. Temporal workflow ID 와 연결.
AntiNukeIncident (antinuke 소유)¶
이상 감지 이벤트 기록.
상세 스키마: data/recovery-schema.md
Aggregates¶
- GuildState (sync) — Guild 별로 roles, channels, permission_overrides 등 하위 entity
- Snapshot (snapshot) — 독립 aggregate, immutable
- RestoreJob (restore) — 독립 aggregate
- AntiNukeIncident (antinuke) — 독립 aggregate
Invariants (Recovery 전역)¶
- 스냅샷 원자성 — 생성 트랜잭션 안에서 완전히 성공 또는 실패
- Restore 는 diff preview 후 실행 — UX 에서 강제
- 메시지 본문 미저장 — ADR-0027 원칙
- Plan 기반 retention — 초과 스냅샷 자동 삭제 (cron)
- AntiNuke 긴급 알림 우선 — Notification preference 무시 가능
Domain events (종합)¶
네 sub-context 가 발행하는 이벤트를 종합. 상세는 각 sub-context 문서 참조.
Published¶
| Event | Source | Payload | Subscribers |
|---|---|---|---|
SnapshotCreated | snapshot | snapshot_id, guild_id, trigger | Audit |
SnapshotDeleted | snapshot | snapshot_id, guild_id | Audit |
RestoreStarted | restore | restore_job_id, snapshot_id, guild_id | Notification, Audit |
RestoreCompleted | restore | restore_job_id, result_summary | Notification, Audit |
RestoreFailed | restore | restore_job_id, error_summary | Notification, Audit |
AntiNukeTriggered | antinuke | guild_id, pattern, suspects | Notification (긴급), Audit |
AntiNukeActioned | antinuke | guild_id, actions_taken | Notification, Audit |
MemberRestored | restore | member_id, guild_id, restored_roles | Audit |
Consumed¶
| Source Event | Action |
|---|---|
BotInstalled | Live Sync 활성화 |
BotKicked | Live Sync 중단, 이후 스냅샷 불가 |
LicenseSuspended | 기능 비활성화 (Plan 에 따라) |
| Discord Gateway events (GUILD_, MEMBER_, ROLE_, CHANNEL_) | Sync 가 직접 수신 (Outbox 경유 아님) |
Ports (Recovery 전역)¶
Inbound¶
// engine/recovery/port/service.go
type Service interface {
// Sync
EnqueueDiscordEvent(ctx, guildID, event DiscordEvent) error
FlushGuildBuffer(ctx, guildID) error // asynq trigger
// Snapshot
CreateSnapshot(ctx, guildID, trigger SnapshotTrigger, options) (*Snapshot, error)
ListSnapshots(ctx, guildID, options) ([]*Snapshot, error)
DeleteSnapshot(ctx, snapshotID) error
// Restore
PreviewRestore(ctx, snapshotID, scope RestoreScope) (*RestoreDiff, error)
StartRestore(ctx, snapshotID, scope, options) (*RestoreJob, error)
GetRestoreJob(ctx, jobID) (*RestoreJob, error)
// AntiNuke
StartAntiNukeWorkflow(ctx, guildID) error // Bot 설치 시
StopAntiNukeWorkflow(ctx, guildID) error
SignalAntiNukeEvent(ctx, guildID, event AntiNukeEvent) error
ListIncidents(ctx, guildID, options) ([]*AntiNukeIncident, error)
}
Outbound¶
type GuildStateRepository interface { ... }
type SnapshotRepository interface { ... }
type RestoreJobRepository interface { ... }
type AntiNukeIncidentRepository interface { ... }
type DiscordClient interface {
// 길드 현재 상태 fetch (restore preview)
FetchGuildState(ctx, discordGuildID) (*DiscordGuildState, error)
// Restore actions
CreateRole(ctx, ...) error
UpdateRole(ctx, ...) error
DeleteRole(ctx, ...) error
// ... channels, permission overrides, guild settings
// AntiNuke response
RevokeMemberRoles(ctx, guildID, userID) error
FetchAuditLog(ctx, guildID, options) ([]AuditLogEntry, error)
}
type TemporalClient interface {
StartRestoreWorkflow(ctx, input RestoreInput) (workflowID string, err error)
StartAntiNukeWorkflow(ctx, guildID) error
SignalWorkflow(ctx, workflowID, signalName, payload) error
QueryWorkflow(ctx, workflowID, queryName) (any, error)
}
type EventBuffer interface {
// Redis-backed per-guild buffer
Push(ctx, guildID, event) error
Pop(ctx, guildID, max) ([]Event, error)
Length(ctx, guildID) (int, error)
}
type EventPublisher interface {
Publish(ctx, event DomainEvent) error // Outbox
}
type LicensingReader interface {
Can(ctx, guildID, feature) (bool, error)
GetActiveLicense(ctx, guildID) (*License, *Plan, error)
}
App layer¶
각 sub-context 는 자기만의 Service 구현을 갖는다. 예:
// engine/recovery/subdomain/snapshot/app/service.go
type snapshotServiceImpl struct {
repo SnapshotRepository
state GuildStateRepository
discord DiscordClient
events EventPublisher
licensing LicensingReader
}
Adapters¶
- Persistence —
engine/recovery/subdomain/*/adapter/persistence/ - Discord —
engine/recovery/subdomain/*/adapter/discord/ - Temporal —
engine/recovery/subdomain/{restore,antinuke}/adapter/temporal/ - Event buffer —
engine/recovery/subdomain/sync/adapter/redis/ - Event publisher —
engine/recovery/adapter/event/(공유) - Licensing reader —
engine/recovery/adapter/licensing/(공유)
Permission model¶
Recovery 기능 접근은 Licensing 이 관리:
| Feature | Free | Pro | Enterprise |
|---|---|---|---|
| Live Sync | ❌ | ✅ | ✅ |
| Manual Snapshot | ❌ | ✅ (1개) | ✅ (3개) |
| Scheduled Snapshot | ❌ | ✅ (7일 보관) | ✅ (30일 보관) |
| Restore | ❌ | ✅ | ✅ |
| Multiple Restore Points | ❌ | ❌ | ✅ |
| AntiNuke Detect | ❌ | ✅ | ✅ |
| AntiNuke Auto-Action | ❌ | ❌ | ✅ (opt-in) |
권한 체크는 모든 Recovery 유스케이스 진입점에서 licensing.Can() 호출.
Failure modes (종합)¶
세부는 각 sub-context 문서에서 상세. 전역 레벨:
- Bot 길드 부재 — Restore/Snapshot 불가, 명확한 에러 반환
- Temporal Server 장애 — Restore/AntiNuke 시작 불가. 기존 workflow 는 서버 복구 후 재개
- Discord API 전체 장애 — Sync/Snapshot 영향, Temporal Activity 가 retry
- 플랜 다운그레이드 중 기능 사용 — Licensing 에서 즉시 차단
See also¶
domain/recovery/sync.md— Live Sync 상세domain/recovery/snapshot.md— Snapshot 상세domain/recovery/restore.md— Restore 상세domain/recovery/antinuke.md— AntiNuke 상세data/recovery-schema.md— DB 스키마architecture/context-map.md— 다른 도메인과의 관계adr/0014-recovery-temporal-workflow.md— Temporal 채택adr/0019-hexagonal-pragmatic.md— Core Hexagonaladr/0024-restore-mode-sync.md— Sync 모드adr/0025-snapshot-jsonb-storage.md— JSONB 저장adr/0026-antinuke-mvp-inclusion.md— AntiNuke MVP 포함adr/0027-message-content-exclusion.md— 메시지 제외ㄴㄴ