5.5 KiB
Feature Specification: Tenant Portfolio & Context Switch (031)
Feature Branch: feat/031-tenant-portfolio-context-switch
Created: 2026-01-04
Status: Implemented (ready to merge)
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
userstable 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_IDcan 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
- Tenants list shows only tenants the user can access.
- Portfolio shows environment badge (PROD/DEV/STAGING/OTHER) and connection/health indicators.
- 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
- Switching tenant updates the visible Current Tenant badge and redirects to a default tenant-scoped landing page (e.g. Policies).
- Policies, Backups, Restore Runs, and Policy Versions are scoped to the selected tenant.
- 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
- Bulk “Sync selected” dispatches a sync job per tenant (batch) and shows progress.
- 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
- Attempting to open a tenant without access is denied (403) and does not change Current Tenant.
- 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.