# 12 — Migration Guide (Part 10) Moving the current **single-account Gmail** app to the **multi-provider, multi-user** model — **additive and reversible**, no destructive step. Executed as ordered EF Core migrations + idempotent backfills, behind a short maintenance window. ## Principles - **Additive first:** create new tables/columns before moving data; keep old columns until parity is verified. - **Idempotent backfills:** safe to re-run; keyed on stable ids. - **Flag-gated cutover:** the new sign-in/model activates behind flags; the old path stays until removed. - **Reversible:** each step has a documented rollback; no data is deleted during migration. ## Step-by-step 1. **Schema (additive migration)** - Create `users`, `accounts`, `provider_tokens`, `sessions`, `user_settings`, `system_settings`, `feature_flags`, `audit_logs`. - Add `account_id`, `user_id` (nullable) to the current email/thread tables; widen `search_vector`; add nullable `embedding vector(768)` + `pgvector` extension. 2. **Identity backfill** - For the existing operator/user, create a `users` row; mark the **first user = Admin**. - Create one **`Google` `account`** per existing identity (`is_login_identity=true`); move current encrypted Gmail tokens → `provider_tokens`. 3. **Email backfill** - Set `account_id`/`user_id` on all existing `Email`/thread rows to the default Google account. - Regenerate the widened `search_vector`; leave `embedding` null (backfilled later by the AI phase). - Enforce the new unique keys `(account_id, provider_message_id)` / `(account_id, provider_thread_id)`. 4. **Config seed** - Seed `feature_flags`: `provider.google=on`, `provider.microsoft=off`, `provider.imap=off`, `ai.enabled` = derived from the current `Ai:Mode` (Disabled→off), `ai.*`=off, `maintenance.*`=off. - Create `system_settings` singleton; create `user_settings` from any existing per-user prefs (else defaults). 5. **Cutover** - Enable the new OAuth-as-login + unified sync behind their flags; verify on **staging** first (the pipeline we built), then production via a tagged release. 6. **Cleanup (later, separate migration)** - Once parity is confirmed in production, drop obsolete columns/paths. Not part of the cutover. ## Verification checklist - Existing user signs in via Google → lands on their mail unchanged. - Email counts match pre/post; search returns identical results for sample queries. - Tokens decrypt and refresh; sync resumes from the correct cursor. - No cross-user rows visible (isolation test). ## Rollback - **Pre-cutover:** additive changes are inert → simply don't flip the flags; drop new tables if aborting. - **Post-cutover issue:** flip flags off / redeploy previous **tag**; old columns still present → the legacy path still works. No data was deleted, so no data rollback is needed. ## Provider-app prerequisites (operator setup) - **Google:** OAuth client (existing) + redirect `/(…)/signin/google`; scopes `gmail.readonly gmail.modify`. - **Microsoft:** register an Entra app; redirect `/(…)/signin/microsoft`; scopes `Mail.Read Mail.ReadWrite offline_access`; admin consent if required. - **IMAP (future):** per-account host/credentials; validated against the SSRF allowlist. - Because providers are flag-gated, an unconfigured provider is simply hidden — configure, then enable.