spec: tenant portfolio & context switch (031)

This commit is contained in:
Ahmed Darrazi 2026-01-04 14:53:48 +01:00
parent 817ad208da
commit d9db054bfd
4 changed files with 171 additions and 0 deletions

View File

@ -0,0 +1,14 @@
# Requirements Checklist (031)
**Created**: 2026-01-04
**Feature**: [spec.md](../spec.md)
- [ ] Tenant memberships/roles exist and are enforced.
- [ ] Current Tenant context is per-user and always visible.
- [ ] Portfolio shows only accessible tenants with environment + health/status.
- [ ] “Open tenant” changes context and redirects into tenant-scoped area.
- [ ] Tenant-scoped resources are filtered by context and deny unauthorized access.
- [ ] Bulk “Sync selected” dispatches per-tenant jobs and is role-gated.
- [ ] Restore flows show target tenant + environment and require tenant-aware confirmation.
- [ ] Pest tests cover authorization + context switching + bulk actions.

View File

@ -0,0 +1,34 @@
# Plan: Tenant Portfolio & Context Switch (031)
**Branch**: `feat/031-tenant-portfolio-context-switch`
**Date**: 2026-01-04
**Input**: [spec.md](./spec.md)
## Approach
1. Decide on the tenant context mechanism:
- Preferred: Filament tenancy (tenant in URL) + built-in tenant switcher.
- Fallback: session-based Current Tenant + visible banner (avoid global/DB state as “source of truth”).
2. Add data model pieces:
- tenant membership/role mapping
- tenant environment attribute
- optional user preferences (favorites + last used)
3. Implement a single TenantContext resolver (HTTP + console) and central authorization gate/policy:
- deny-by-default if no access
- keep `INTUNE_TENANT_ID` as console override for automation
4. Update tenant-scoped resources/services to use TenantContext instead of `Tenant::current()` and ensure base queries are tenant-scoped.
5. Extend `TenantResource` into a portfolio view:
- access-scoped query
- environment/health columns
- “Open” action + “Sync” action
- bulk “Sync selected”
6. Add restore guardrails:
- target tenant badge/header on restore pages
- type-to-confirm includes tenant/environment (e.g. `RESTORE PROD`)
7. Add targeted Pest tests for authorization, context switching, and bulk sync.
8. Run Pint + targeted tests; document rollout/migration notes.
## Decisions / Notes
- Avoid a global `tenants.is_current` UI context (unsafe for MSP); prefer per-user context.
- Avoid storing Current Tenant in the `users` table as the source of truth (cross-tab risk); prefer route/session context, optionally persisting “last used” separately.
- Start with user-based tenant memberships; extend to organization/group principals later if needed.
- Prefer deriving portfolio stats via relationships (`withCount`, `withMax`) initially; add denormalized summary columns only if needed for performance.

View File

@ -0,0 +1,89 @@
# 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: <name>” 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.

View File

@ -0,0 +1,34 @@
# Tasks: Tenant Portfolio & Context Switch (031)
**Branch**: `feat/031-tenant-portfolio-context-switch`
**Date**: 2026-01-04
**Input**: [spec.md](./spec.md), [plan.md](./plan.md)
## Phase 1: Setup
- [x] T001 Create spec/plan/tasks and checklist.
## Phase 2: Research & Design
- [ ] T002 Review Filament tenancy support and choose the context mechanism (route vs session).
- [ ] T003 Define tenant access roles and mapping (user memberships; future org/group principals).
- [ ] T004 Decide how to store `environment` (column vs JSONB) and whether MSP “customer grouping” is in scope.
- [ ] T005 Define context precedence rules (env override, route tenant, session/default tenant) and cross-tab safety expectations.
## Phase 3: Tests (TDD)
- [ ] T006 Authorization: user cannot open unauthorized tenant (403).
- [ ] T007 Authorization: tenant-scoped resources deny cross-tenant access via URL (403/404).
- [ ] T008 Context switching: “Open tenant” sets context and tenant-scoped pages filter correctly.
- [ ] T009 Bulk sync: dispatches one job per selected tenant; readonly role cannot run it.
- [ ] T010 UI (optional browser tests): tenant switcher visible and environment badge shown.
## Phase 4: Implementation
- [ ] T011 Add migrations for tenant memberships/roles and environment attribute (and optional preferences).
- [ ] T012 Implement `TenantContext` + authorization gate/policy (`canAccessTenant`).
- [ ] T013 Integrate tenant switcher into Filament topbar and make Current Tenant always visible.
- [ ] T014 Scope tenant resources (Policies/Backups/RestoreRuns/etc.) via TenantContext; replace direct `Tenant::current()` usage.
- [ ] T015 Update `TenantResource` into a portfolio view: access-scoped query, columns, filters, “Open”, “Sync”, bulk “Sync selected”.
- [ ] T016 Add restore guardrails (target tenant header + tenant-aware confirmations).
## Phase 5: Verification
- [ ] T017 Run targeted tests.
- [ ] T018 Run Pint (`./vendor/bin/pint --dirty`).