4ce2df0a2b
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 18s
CI / backend (pull_request) Successful in 52s
CI / frontend (pull_request) Successful in 15s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s
3.7 KiB
3.7 KiB
04 — Settings & Feature Flags (Part 3)
Two layers of configuration — per-user preferences and org-wide system settings / feature flags — with a clear precedence. Critically: AI is governed by a feature flag (admin, global), not merely a user preference.
User settings (per user)
Stored in user_settings; editable by the user.
| Setting | Values |
|---|---|
theme |
system · light · dark (dark-first default) |
inbox_layout |
density, pane layout, default view/lane, per-account or unified |
notifications |
channels, quiet hours, priority-only |
ai_prefs |
per-feature opt-in (summaries, replies, semantic, ask-inbox…) — only effective if the flag allows |
provider_prefs |
default account, sync frequency, signature per account |
System settings (admin, org-wide)
Stored in system_settings (singleton); Admin-only (05).
maintenance_mode(off · read-only · locked-except-admin) ·default_theme·registration_open· org display name · retention defaults.
Feature flag system
feature_flags rows: key · enabled · scope · rollout · description · updated_by/at.
scope = SystemOnly— a hard org-wide switch; users cannot override (e.g.,provider.microsoft,maintenance.readonly).scope = UserOverridable— a default that a user preference can turn off (never on beyond what the flag permits) — e.g.,ai.summaries.rollout(jsonb) — optional per-role/percentage gating (e.g., enable a beta for Admins first).
Evaluation service
public interface IFeatureFlags {
bool IsEnabled(string key, UserContext user); // system flag ∧ scope ∧ role rollout
}
public interface IAiGate { // the AI-specific resolver
bool IsAiEnabled(UserContext user); // ai.enabled (system) ∧ user.ai_prefs.master
bool IsAiFeatureEnabled(string feature, UserContext user); // ∧ ai.<feature> ∧ user opt-in
}
- Flags are cached with change notification (hot-reload on admin edit); every read is cheap.
- All flag reads are fail-closed: unknown/errored flag ⇒ treated as off.
AI gating precedence (the key requirement)
Effective AI availability is an AND down a chain — the system flag is the master gate:
AI feature X is available for user U ⇔
feature_flags["ai.enabled"].enabled (admin master switch — SYSTEM)
∧ feature_flags["ai." + X].enabled (per-feature flag — SYSTEM)
∧ providerCapability(X) (Ollama/provider actually available)
∧ user.ai_prefs.master_opt_in (user hasn't disabled AI for themselves)
∧ user.ai_prefs[X] (user opted into this feature)
- Admin turns
ai.enabledoff ⇒ AI vanishes for everyone, regardless of any user preference. This is the behaviour the brief mandates. - With AI on at the system level, users still choose per-feature. The Null AI provider + capability flags (blueprint 06) mean a disabled path falls back or hides — never errors, never blocks core email.
Settings precedence (general)
system default → feature flag (may hard-disable) → user override (only where UserOverridable)
Maintenance mode
read-only: mutations (send/cleanup/label) blocked with a banner; browsing/search stay up.locked-except-admin: only Admins can use the app (for migrations/upgrades).- Enforced at the API via a middleware policy + surfaced as a global banner in the UI.
Auditing
Flag and system-setting changes are admin actions → audit-logged (who/what/old→new/when).