# Guild Settings Module - `updateSetting()` uses a hardcoded `keyMap` to map friendly key names to DB columns. Use exact key names (e.g., `"studentRole"` not `"studentRoleId"`). Unknown keys throw `UserError`. - Type coercion per column: Discord IDs → BigInt automatically; `colorRoleIds` must be array; `featureOverrides` must be object; `moderationDmOnWarn` must be boolean; `moderationAutoTimeoutThreshold` must be number. Null values set columns to NULL. - **Caching:** `getGuildConfig()` (in `shared/lib/config.ts`) caches transformed settings for 60 seconds. Every mutation (`upsertSettings`, `updateSetting`, `addColorRole`, `removeColorRole`) calls `invalidateGuildConfigCache(guildId)` immediately. - If settings don't exist for a guild, the cache returns safe defaults — no errors thrown. - `featureOverrides` is a sparse `Record` — no keys are predefined. Consumers must check key existence. - **No Discord validation:** The service does not verify that role/channel IDs actually exist in Discord. Invalid IDs are stored silently. - `addColorRole()` / `removeColorRole()` fetch the full settings, mutate the array in JS, then upsert — this is not atomic and can race under concurrent requests. - `terminalMessageId` and `terminalChannelId` are separate DB columns but grouped as `terminal: { channelId, messageId }` in the cached config. Setting one without the other can create orphaned data.