콘텐츠로 이동

Guild

Umbra 가 보호 대상으로 관리하는 Discord 길드 도메인. 봇 설치부터 길드 설정, owner 관리까지 담당한다. Licensing 과 Recovery 의 적용 대상 단위이다.

Bounded context

  • Type — Supporting
  • Sibling contexts — Identity (owner 참조), Member, Licensing (권한 적용 대상), Recovery
  • Location in codebaseengine/guild/

Why this domain exists

Umbra 는 "Guild 단위 구독" (Hybrid 모델, ADR-0011) 을 채택했다. 즉 길드가 복구, 권한, 감사의 기본 단위다. Discord 의 Guild 는 외부 entity 이지만 Umbra 는 이를 내부 모델로 반영하여 다음을 관리한다.

  • 봇 설치 상태 — Umbra Bot 이 해당 길드에 있는가
  • 길드 메타데이터 — 이름, 아이콘, slug (웹 조인 URL 용) 등
  • Owner 추적 — 현재 owner 가 누구이며 변경 이력
  • 설정 — 길드별 Umbra 설정 (알림 채널, AntiNuke 임계값 등)

Discord Guild ID 를 직접 PK 로 쓰지 않고 내부 UUID 를 두는 이유는 일관성 (ADR-0021) + 길드 재생성 추적.

Domain model

Guild

Guild
├─ id               UUID v7       PK
├─ discord_guild_id TEXT          Discord snowflake, UNIQUE
├─ name             TEXT          (캐시, Discord source of truth)
├─ icon_hash        TEXT          (캐시)
├─ slug             TEXT          웹 조인 URL 용, UNIQUE
├─ owner_user_id    UUID          → identity.users.id (nullable, owner 가 Umbra 가입 안 했을 수 있음)
├─ owner_discord_id TEXT          항상 저장 (Umbra 미가입 owner 대응)
├─ bot_installed_at TIMESTAMPTZ
├─ bot_removed_at   TIMESTAMPTZ   봇 강퇴 시
├─ created_at       TIMESTAMPTZ
└─ updated_at       TIMESTAMPTZ

GuildConfig

길드별 Umbra 설정. Guild 와 1:1.

GuildConfig
├─ guild_id             UUID v7   PK, → guilds.id
├─ notification_channel_id  TEXT  Umbra 알림을 보낼 채널
├─ web_join_auto_role_id    TEXT  자동 역할 부여 대상 (옵션)
├─ antinuke_enabled     BOOLEAN   활성 여부 (Pro 이상)
├─ antinuke_auto_action BOOLEAN   자동 대응 활성화 (Enterprise)
├─ antinuke_thresholds  JSONB     감지 임계값 커스터마이징
└─ updated_at           TIMESTAMPTZ

Aggregates

  • Guild aggregate — Guild + GuildConfig

Invariants

  • Discord Guild 1:1UNIQUE(discord_guild_id) DB 제약
  • slug 유일성 — 웹 조인 URL 충돌 방지, UNIQUE(slug)
  • slug 형식 — 소문자 영숫자 + 하이픈, 3~30자
  • 봇 제거 시 bot_removed_at 설정 — Guild row 는 유지 (복구 시 재사용)
  • GuildConfig 기본값 — 길드 등록 시 자동 생성 (빈 설정 아님)

State machine

stateDiagram-v2
    [*] --> Active : Bot installed
    Active --> Removed : Bot kicked
    Removed --> Active : Bot re-installed
    Active --> Deleted : Guild deleted on Discord
    Removed --> Deleted : Guild deleted on Discord
  • Active — 봇이 길드에 있고 정상 운영
  • Removed — 봇이 강퇴됨. License/Subscription 자동 suspend.
  • Deleted — Discord 에서 길드 자체 삭제. License/Subscription 자동 cancel.

Domain events

Published

Event Trigger Payload Subscribers
BotInstalled 봇이 길드에 설치됨 guild_id, installer_user_id, discord_guild_id Licensing (Free License 생성), Audit
BotKicked 봇이 길드에서 강퇴됨 guild_id Licensing (suspend), Billing (suspend), Audit
GuildOwnerChanged 길드 owner 변경 감지 guild_id, old_owner_id, new_owner_id, old_discord_id, new_discord_id Audit
GuildDeleted 길드 자체 삭제 guild_id Licensing (cancel), Billing (cancel), Audit
GuildConfigUpdated 길드 설정 변경 guild_id, changed_fields Audit

Consumed

Guild 는 다른 도메인 이벤트를 구독하지 않는다.

Ports

Supporting 도메인이므로 단순 구조.

Inbound

type Service interface {
    RegisterGuild(ctx, discordGuildID, installerDiscordID) (*Guild, error)
    MarkBotRemoved(ctx, discordGuildID) error
    MarkGuildDeleted(ctx, discordGuildID) error
    UpdateOwner(ctx, discordGuildID, newOwnerDiscordID) error
    GetGuild(ctx, guildID) (*Guild, error)
    GetGuildByDiscordID(ctx, discordGuildID) (*Guild, error)
    GetGuildBySlug(ctx, slug) (*Guild, error)
    GenerateSlug(ctx, guildID) error  // 슬러그 생성/재생성
    UpdateConfig(ctx, guildID, config) error
}

Outbound

  • GuildRepository — sqlc 래퍼
  • DiscordClient — 길드 메타데이터 조회 (Discord REST API)

Adapters

  • Persistenceengine/guild/repository.go — sqlc 기반
  • Discordengine/guild/discord_client.go — Discord REST 호출

Guild 는 Supporting 이므로 별도 adapter/ 하위 디렉토리 없이 평면 구조 허용.

Slug generation

웹 조인 URL (join.umbra.ink/{slug}) 에 사용되는 slug 는 다음 규칙으로 생성.

  1. Discord 길드 이름을 slugify (공백 → -, 특수문자 제거, 소문자)
  2. 3~30자 제한 초과 시 잘라냄
  3. 충돌 시 -{random4} 접미사 추가
  4. 길드 owner 가 수동 변경 가능 (/guild config slug)

Permission model

Guild 관련 작업은 Licensing 이 관리하지 않는 Discord 권한 에 의존.

  • Bot 설치 — Discord 의 MANAGE_GUILD 권한자가 초대 링크 사용
  • GuildConfig 수정MANAGE_GUILD 보유자
  • Slug 변경 — Owner 만 (권한 상승 방지)
  • 웹 조인 링크 조회 — Owner 또는 MANAGE_GUILD

권한 체크는 Discord REST API 로 실시간 확인 (캐시는 60초 TTL).

Failure modes

  • 봇이 길드에 없는데 요청 수신 — 401 반환, Guild 는 Removed 상태 유지
  • Owner 가 Umbra 미가입owner_user_id = NULL, owner_discord_id 만 저장. 결제 시작 시점에 OAuth2 유도
  • Slug 충돌 (경쟁 조건) — DB UNIQUE 제약이 실패시키고 재생성
  • Discord API 장애로 길드 메타 갱신 실패 — 기존 캐시 유지, 다음 주기에 재시도

See also

  • data/guild-schema.md — DB 스키마 상세
  • domain/member.md — Guild 멤버
  • domain/licensing.md — Guild 에 적용되는 권한
  • domain/recovery/overview.md — Guild 상태 복구
  • flows/bot-installation.md — 봇 설치 흐름
  • flows/web-join.md — slug 기반 웹 조인
  • adr/0011-hybrid-license-model.md — Guild = 권한 적용 대상