콘텐츠로 이동

ADR-0024: Restore Mode: Sync (Git Reset Style)

Umbra 의 복구 실행은 "Sync 모드" — 스냅샷 시점 상태와 현재 상태의 diff 를 계산하여, 스냅샷에 없는 것은 삭제하고 스냅샷에 있는 것은 생성/업데이트한다. Git reset 과 같은 의미론이다.

Status

Accepted

  • Decided at — 2026-04-13
  • Decided by — Pablo

Context

스냅샷 기반 복구의 의미론은 여러 가지가 가능하다.

  • Add-only — 스냅샷에 있는 요소를 생성 또는 업데이트. 현재 있는 추가 요소는 유지
  • Sync (Git reset) — 스냅샷과 완전히 일치시킴. 현재에만 있는 요소는 삭제
  • Merge — 현재와 스냅샷의 합집합 (충돌 시 사용자 선택)

"서버가 nuke 당했다" 상황에서 사용자가 원하는 것은 무엇인가? 현실 관찰:

  • 대부분 사고 시나리오는 불필요한 것이 추가됨 (공격자가 만든 가짜 역할, 이상한 채널, 스팸 멤버)
  • 사용자는 "스냅샷 시점으로 완전히 되돌려줘" 를 기대
  • Add-only 는 공격자가 만든 요소를 그대로 남기므로 불충분
  • Merge 는 UX 복잡도 과도

Decision

Umbra 의 Restore 는 Sync 모드 로 동작한다.

  • 스냅샷에 없는 요소 → 삭제
  • 스냅샷에 있는 요소 (현재에 없음) → 생성
  • 스냅샷에 있는 요소 (현재에도 있음) → 업데이트 (속성 덮어쓰기)

단, 파괴적 작업이므로 실행 전 diff 미리보기를 사용자에게 제시 한다.

Restoration scope (카테고리별 선택)

복구 범위는 카테고리별로 선택 가능.

  • Roles
  • Channels (Categories + Text/Voice)
  • Permission Overrides
  • Guild Settings (이름, 아이콘, AFK 설정 등)
  • Members (수동 단위 복원만, 자동 복구에서 제외)

의존성 강제:

  • Permission Overrides 복구는 Roles + Channels 복구가 전제
  • UI 에서 의존성 위반 조합 선택 차단

메시지 본문은 복구 대상 아님

ADR-0027 에 의해 메시지는 백업 대상이 아니므로 복구에도 포함되지 않는다.

선택 근거:

  • 사용자 기대 부합 — "nuke 당했을 때 되돌려줘" 의 자연스러운 해석
  • 공격 제거 — 공격자가 추가한 요소가 자동 제거됨
  • 예측 가능성 — 복구 결과가 명확 (스냅샷 상태 그대로)
  • diff 미리보기 — 파괴성을 UX 로 완화

Consequences

Positive

  • 공격 상황에서 Umbra 의 가치가 명확히 발휘
  • 복구 결과가 예측 가능 (스냅샷 = 결과)
  • 운영자가 "복구 후 이상한 거 남아있을까" 걱정 불필요

Negative

  • 파괴적 작업 — 복구 후 원상복구 불가 (자동 pre-restore 스냅샷으로 완화)
  • 사용자가 "지금 추가한 역할 보존하고 복구" 같은 기대 가지면 불일치
  • 대량 삭제로 Discord API rate limit 히트 가능성 ↑

Neutral

  • Diff 미리보기는 대시보드에서 제공
  • 복구 실행 전 옵션으로 "pre-restore 스냅샷 생성" 기본 체크

Restore workflow detail

1. Diff calculation

snapshot_roles = snapshot.roles
current_roles = fetch_from_discord(guild_id).roles

to_delete = current_roles - snapshot_roles  (by discord_role_id)
to_create = snapshot_roles - current_roles
to_update = snapshot_roles ∩ current_roles  (with attribute diff)

Channels, Permission Overrides 도 동일 방식.

2. Preview UI

복구 버튼 클릭 시 diff 를 시각적으로 표시:

Will delete:
  - Role "Spammer" (id: 12345)
  - Channel #nuke-channel
Will create:
  + Role "Moderator" (from snapshot)
Will update:
  ~ Role "Admin" (permissions changed)

사용자가 "확인" 하면 실행.

3. Execution order

순서가 중요. 의존성 역순으로 삭제, 의존성 순서로 생성.

1. Delete permission overrides (현재만 있는 것)
2. Delete channels (현재만 있는 것)
3. Delete roles (현재만 있는 것)
4. Create roles (스냅샷에만 있는 것)
5. Create channels (스냅샷에만 있는 것)
6. Apply permission overrides (스냅샷 기준)
7. Update guild settings

4. Pre-restore snapshot (optional)

복구 실행 전 현재 상태를 자동 스냅샷으로 저장. 기본값: 체크됨.

사용자가 "복구가 잘못됐네" 라고 판단하면 이 pre-restore 스냅샷으로 재복구 가능 → 원상복구.

Alternatives considered

Alternative 1: Add-only (스냅샷에 있는 것만 생성/업데이트)

Pros

  • 파괴적 작업 0 (삭제 없음)
  • "안전한 복구"

Cons

  • 공격자 추가 요소가 그대로 남음 → Umbra 의 핵심 가치 훼손
  • 복구 결과가 "섞인 상태" 로 예측 어려움

Why rejected — Umbra 의 가치 제안(Anti-Nuke 복구)과 정면 충돌.

Alternative 2: Merge mode (사용자가 충돌 조정)

Pros

  • 가장 유연

Cons

  • UX 복잡 (수백 개 요소 충돌 조정)
  • 대부분 사용자는 "그냥 되돌려줘" 원함

Why rejected — MVP UX 범위 초과. Phase 2+ 에서 선택적 옵션으로 검토 가능.

Alternative 3: Dry-run only (실제 실행 없음)

Pros

  • 매우 안전

Cons

  • 복구 기능이 성립하지 않음 (확인만 가능)

Why rejected — 제품 가치 자체 상실.

Alternative 4: Timestamp-based merge (스냅샷 이후 변경은 유지)

Pros

  • 지능적 해결

Cons

  • Discord audit log 의 시간 정확도 의존
  • 악의적 변경과 정상 변경을 구분 못함
  • 구현 복잡

Why rejected — 정확성 보장 어려움.

Compliance

  • 모든 복구는 사용자가 diff preview 확인 후 실행 승인
  • pre-restore 스냅샷 옵션은 기본 ON (체크박스 UI)
  • 의존성 있는 카테고리 조합 선택 UI 에서 강제 (Permission Overrides ⊂ Roles + Channels)
  • 복구 중 progress 를 사용자에게 실시간 노출 (Temporal workflow 상태 조회)
  • 복구 실패 시 자동 롤백은 하지 않음 (Discord API 특성상 원자적 롤백 불가) — 대신 실패 지점 명시 + pre-restore 스냅샷 안내

Revisit triggers

  • 사용자 요구가 Merge mode 에 대해 반복되면 Phase 2 에 추가 검토
  • 복구 실패 롤백 자동화 기술(Discord API 개선)이 가능해지면 재설계

References