Guild¶
Umbra 가 보호 대상으로 관리하는 Discord 길드 도메인. 봇 설치부터 길드 설정, owner 관리까지 담당한다. Licensing 과 Recovery 의 적용 대상 단위이다.
Bounded context¶
- Type — Supporting
- Sibling contexts — Identity (owner 참조), Member, Licensing (권한 적용 대상), Recovery
- Location in codebase —
engine/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:1 —
UNIQUE(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¶
- Persistence —
engine/guild/repository.go— sqlc 기반 - Discord —
engine/guild/discord_client.go— Discord REST 호출
Guild 는 Supporting 이므로 별도 adapter/ 하위 디렉토리 없이 평면 구조 허용.
Slug generation¶
웹 조인 URL (join.umbra.ink/{slug}) 에 사용되는 slug 는 다음 규칙으로 생성.
- Discord 길드 이름을 slugify (공백 →
-, 특수문자 제거, 소문자) - 3~30자 제한 초과 시 잘라냄
- 충돌 시
-{random4}접미사 추가 - 길드 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 = 권한 적용 대상