Files
Inboxintel/docs/discovery/multi-provider/04-settings-and-flags.md
T
cesnimda 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
docs: discovery blueprint + multi-provider design (#8)
2026-07-01 19:59:53 +02:00

69 lines
3.7 KiB
Markdown

# 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](05-admin-system.md)).
- `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
```csharp
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.enabled` off ⇒ 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](../06-ai-strategy.md)) 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).