# Feature Specification: Tenant Portfolio & Context Switch (031) **Feature Branch**: `feat/031-tenant-portfolio-context-switch` **Created**: 2026-01-04 **Status**: Proposed **Risk**: Medium **Priority**: P1 ## Context Today TenantPilot behaves like a single-tenant app: - The “current tenant” is global (`tenants.is_current` + `Tenant::current()`), not per user. - Most tenant-scoped screens implicitly use `Tenant::current()`. This is limiting and potentially unsafe for: - Customers running multiple tenants (PROD/DEV/STAGING). - MSPs managing many customer tenants. We need a tenant-agnostic **Portfolio** view plus an explicit, always-visible **Current Tenant** context for all tenant-scoped areas (Policies, Backups, Restore Runs, etc.). ## Design Considerations (Best Practice) - Prefer an explicit tenant context (route parameter or session) over hidden global state. - Avoid storing Current Tenant in the `users` table as the source of truth (cross-tab risk). If persistence is needed, store **“last used”** separately and treat it as a default for new sessions. - Keep console/automation behavior stable: `INTUNE_TENANT_ID` can remain a console override, but tenant-scoped UI must not depend on it. ## User Scenarios & Testing ### User Story 1 — Portfolio overview (P1) As a user with access to multiple tenants, I can see a portfolio overview with health/status and key counts. **Acceptance Scenarios** 1. Tenants list shows only tenants the user can access. 2. Portfolio shows environment badge (PROD/DEV/STAGING/OTHER) and connection/health indicators. 3. Portfolio columns can be filtered by environment and connection status. ### User Story 2 — Safe tenant context switching (P1) As a user, I can switch the Current Tenant via a topbar switcher or by clicking “Open” in the portfolio, and all tenant-scoped screens reflect that tenant. **Acceptance Scenarios** 1. Switching tenant updates the visible Current Tenant badge and redirects to a default tenant-scoped landing page (e.g. Policies). 2. Policies, Backups, Restore Runs, and Policy Versions are scoped to the selected tenant. 3. Restore flows always show the target tenant and environment prominently and require tenant-aware type-to-confirm. ### User Story 3 — Multi-tenant bulk actions (P2) As an operator, I can select multiple tenants in the portfolio and run safe bulk actions (initially Sync). **Acceptance Scenarios** 1. Bulk “Sync selected” dispatches a sync job per tenant (batch) and shows progress. 2. Readonly users cannot trigger bulk sync. ### User Story 4 — Authorization hardening (P1) As a user, I cannot access tenants or tenant-scoped data I am not authorized for. **Acceptance Scenarios** 1. Attempting to open a tenant without access is denied (403) and does not change Current Tenant. 2. Direct URL access to tenant-scoped pages for an unauthorized tenant returns 403/404. ## Requirements ### Functional Requirements - **FR-001**: Introduce a per-user Current Tenant context for all tenant-scoped screens. - **FR-002**: Current Tenant context must be always visible in the UI (topbar) to reduce “wrong tenant” operations. - **FR-003**: Add an “Open” action from the portfolio to set Current Tenant and redirect into the tenant-scoped area. - **FR-004**: Portfolio view is tenant-agnostic and supports filtering, search, and safe bulk actions. - **FR-005**: Tenant access is enforced centrally (single `canAccessTenant(...)` gate/policy used by UI + routes + services). - **FR-006**: Restore remains single-tenant; restore actions must include explicit tenant/environment confirmations and never rely on hidden global context. - **FR-007**: Bulk Sync is tenant-safe: per-tenant authorization, per-tenant job execution, and audit logs for each tenant sync trigger. ### UX / UI Requirements - **UX-001**: Topbar shows “Tenant: ” with an environment badge (PROD/DEV/STAGING/OTHER) and is accessible from all tenant-scoped pages. - **UX-002**: Tenant switcher is searchable (typeahead); favorites (if enabled) appear at the top. - **UX-003**: Portfolio table includes (at minimum): Name, Tenant ID (short/copy), Environment, Connection/App status, RBAC/Health indicator, Last Sync (time), Policies count; optional Restore runs (last 30d). - **UX-004**: Portfolio “Open” action makes the tenant context explicit and navigates into the tenant-scoped area. - **UX-005**: Restore screens show “Target Tenant” prominently (name + environment badge) and require tenant-aware type-to-confirm (e.g. `RESTORE PROD`). ### Data Model Requirements - **DM-001**: Introduce tenant access/membership mapping (user ↔ tenant) with a role (`owner|manager|operator|readonly`). - **DM-002**: Add tenant environment classification (`prod|dev|staging|other`) as a first-class attribute (column or indexed JSONB). - **DM-003 (Optional)**: Persist per-user tenant preferences (favorites + last used) without coupling it to cross-tab safety. - **DM-004 (Optional)**: Support grouping tenants by customer (MSP use case) via a lightweight “customer label” or a dedicated Customer model (future). ## Non-Goals - No multi-tenant policy detail view in one screen. - No multi-tenant restore; restore run always targets exactly one tenant. - No cross-tenant diff/promotion (separate feature). ## Success Criteria - **SC-001**: A user can switch Current Tenant quickly and always understands which tenant they are operating on. - **SC-002**: All tenant-scoped data is strictly filtered and authorization-safe. - **SC-003**: Bulk Sync works across selected tenants with clear feedback and role gating.