spec: tenant portfolio & context switch (031)
This commit is contained in:
parent
817ad208da
commit
d9db054bfd
@ -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.
|
||||
|
||||
34
specs/031-tenant-portfolio-context-switch/plan.md
Normal file
34
specs/031-tenant-portfolio-context-switch/plan.md
Normal 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.
|
||||
89
specs/031-tenant-portfolio-context-switch/spec.md
Normal file
89
specs/031-tenant-portfolio-context-switch/spec.md
Normal 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.
|
||||
34
specs/031-tenant-portfolio-context-switch/tasks.md
Normal file
34
specs/031-tenant-portfolio-context-switch/tasks.md
Normal 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`).
|
||||
|
||||
Loading…
Reference in New Issue
Block a user