콘텐츠로 이동

Recovery Overview

Recovery 는 Umbra 의 핵심 Core 도메인. 길드 상태를 실시간 미러링하고, 스냅샷으로 보존하며, 사고 시 복구한다. 네 개의 sub-context 로 구성된다: Sync, Snapshot, Restore, AntiNuke. 이 문서는 Recovery 전체 구조를 설명한다.

Bounded context

  • TypeCore (full Hexagonal)
  • Sibling contexts — Licensing (기능 게이팅), Guild (복구 대상), Member (복원 대상)
  • Location in codebaseengine/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

  • Persistenceengine/recovery/subdomain/*/adapter/persistence/
  • Discordengine/recovery/subdomain/*/adapter/discord/
  • Temporalengine/recovery/subdomain/{restore,antinuke}/adapter/temporal/
  • Event bufferengine/recovery/subdomain/sync/adapter/redis/
  • Event publisherengine/recovery/adapter/event/ (공유)
  • Licensing readerengine/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 Hexagonal
  • adr/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 — 메시지 제외ㄴㄴ