Implements Spec 080: split Filament into workspace-managed `/admin/*` (manage) vs tenant operations `/admin/t/{tenant}/*` (operate).
Highlights:
- Adds tenant operations panel (`tenant`) at `/admin/t` with tenancy by `Tenant.external_id`
- Keeps management resources in workspace panel (`admin`) under `/admin/tenants/*`
- Moves Provider Connections to workspace-managed routes: `/admin/tenants/{tenant}/provider-connections`
- Adds discoverability CTA on tenant view (Actions → Provider connections)
- Adds/updates Pest regression tests for routing boundaries, 404/403 RBAC-UX semantics, and global search isolation
- Includes full Spec Kit artifacts under `specs/080-workspace-managed-tenant-admin/`
Validation:
- `vendor/bin/sail bin pint --dirty`
- `vendor/bin/sail artisan test --compact tests/Feature/Spec080WorkspaceManagedTenantAdminMigrationTest.php`
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #97
239 lines
15 KiB
Markdown
239 lines
15 KiB
Markdown
# Feature Specification: Workspace-Managed Tenant Administration Migration
|
||
|
||
**Feature Branch**: `080-workspace-managed-tenant-admin`
|
||
**Created**: 2026-02-07
|
||
**Status**: Draft (implementation-ready)
|
||
**Input**: User description: "Make Manage workspace-scoped (/admin) and Operate tenant-scoped (/admin/t/{tenant}). Eliminate management CRUD from /admin/t/*"
|
||
|
||
This feature migrates all tenant administration surfaces out of the Filament tenant scope (`/admin/t/{tenant}/*`) into workspace-scoped routes (`/admin/*`). Tenant scope is reserved strictly for operational modules.
|
||
|
||
**Separation rule (normative):**
|
||
- Workspace panel (`/admin/*`) = Manage (tenants, memberships, provider connections, required permissions, onboarding/activation, monitoring).
|
||
- Tenant panel (`/admin/t/{tenant}/*`) = Operate (inventory, drift, policies, backups, directory, etc.).
|
||
|
||
**Out of scope for this feature:** introducing a new tenant panel “Home” page at `/admin/t/{tenant}`.
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-02-07
|
||
|
||
- Q: Should management pages be viewable for workspace members without manage capabilities? → A: Yes. Management pages are viewable for workspace members; only mutations are capability-gated (403).
|
||
- Q: Should this feature include a dedicated tenant panel “Home” page at `/admin/t/{tenant}`? → A: No. Do not add a new tenant home page in this feature.
|
||
- Q: For workspace-managed routes like `/admin/tenants/{tenant}`, what should `{tenant}` be? → A: `Tenant.external_id` (Entra tenant GUID), same identifier used by Filament tenancy under `/admin/t/{tenant}`.
|
||
- Q: Should “Managed Tenants” appear in Filament Global Search? → A: Yes, in the workspace panel only; tenant panel must not expose tenant-management entities in search.
|
||
- Q: For workspace-managed tenant management routes, what is the canonical URL shape? → A: `/admin/tenants*` (workspace is selected in context/session; middleware enforces it), not `/admin/w/{workspace}/tenants*`.
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
<!--
|
||
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||
|
||
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||
Think of each story as a standalone slice of functionality that can be:
|
||
- Developed independently
|
||
- Tested independently
|
||
- Deployed independently
|
||
- Demonstrated to users independently
|
||
-->
|
||
|
||
### User Story 1 - Manage tenants from workspace scope (Priority: P1)
|
||
|
||
As a workspace member, I can manage (view/create/configure) managed tenants under `/admin/tenants*` without needing to enter tenant scope.
|
||
|
||
**Why this priority**: It removes the “henne-ei” context issue and makes `/admin` the canonical management surface.
|
||
|
||
**Independent Test**: Fully testable via HTTP requests asserting 200/404 and basic page visibility under `/admin/tenants*`.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** I am a workspace member, **When** I visit `/admin/tenants`, **Then** I can access the Tenants management list.
|
||
2. **Given** I am a workspace member, **When** I visit `/admin/tenants/{tenant}`, **Then** I can access the Tenant management overview.
|
||
3. **Given** I am not a workspace member, **When** I visit `/admin/tenants` or `/admin/tenants/{tenant}`, **Then** I receive a 404 (deny-as-not-found).
|
||
|
||
---
|
||
|
||
### User Story 2 - Operate tenant modules only when entitled (Priority: P2)
|
||
|
||
As a user, I can operate inside a managed tenant under `/admin/t/{tenant}/*` only when I’m entitled to that tenant.
|
||
|
||
**Why this priority**: It preserves tenant isolation and makes 404/403 semantics predictable.
|
||
|
||
**Independent Test**: Fully testable via an operational page route asserting 200 for entitled users and 404 for non-entitled users.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** I am a workspace member and entitled to the selected tenant, **When** I visit an operational route under `/admin/t/{tenant}/*`, **Then** I can access it.
|
||
2. **Given** I am a workspace member but not entitled to the selected tenant, **When** I visit an operational route under `/admin/t/{tenant}/*`, **Then** I receive a 404 (deny-as-not-found).
|
||
|
||
---
|
||
|
||
### User Story 3 - Tenant-scoped management routes are removed (Priority: P3)
|
||
|
||
As a user, I cannot access tenant-scoped management CRUD routes anymore; they are removed and should not resolve.
|
||
|
||
**Why this priority**: It enforces IA separation via route registration (not just UI hiding).
|
||
|
||
**Independent Test**: Fully testable via HTTP 404 assertions for a set of removed routes.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** any user, **When** I request `/admin/t/{tenant}/provider-connections`, **Then** I receive 404 because the route does not exist.
|
||
2. **Given** any user, **When** I request `/admin/t/{tenant}/required-permissions`, **Then** I receive 404 because the route does not exist.
|
||
3. **Given** any user, **When** I request a tenant-scoped tenant-management route (e.g. `/admin/t/{tenant}/tenants/*`), **Then** I receive 404 because the route does not exist.
|
||
|
||
---
|
||
|
||
### User Story 4 - Management actions enforce capability semantics (Priority: P2)
|
||
|
||
As a workspace member, I can see management pages, but mutations are forbidden unless I have the required capability.
|
||
|
||
**Why this priority**: It preserves enterprise RBAC semantics (404 for non-membership; 403 for missing capability on mutation).
|
||
|
||
**Independent Test**: Test a representative management mutation and assert 403 when capability is missing.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** I am a workspace member without the relevant manage capability, **When** I attempt a management mutation (e.g., change role, set default connection), **Then** the server responds 403.
|
||
|
||
---
|
||
|
||
### User Story 5 - Global search isolation (Priority: P2)
|
||
|
||
As a user, global search does not leak tenant management entities across scopes.
|
||
|
||
**Why this priority**: It prevents discovery leaks and aligns with deny-as-not-found semantics.
|
||
|
||
**Independent Test**: Test that workspace global search can find allowed tenants; tenant panel search does not expose tenant-management entities; non-members discover nothing.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** I am a workspace member, **When** I use workspace global search, **Then** I can find tenants I can access.
|
||
2. **Given** I am in tenant panel, **When** I use global search, **Then** it does not expose tenant-management entities.
|
||
3. **Given** I am not a workspace member, **When** I use global search, **Then** I do not discover tenant existence.
|
||
|
||
---
|
||
|
||
### Edge Cases
|
||
|
||
- Direct navigation to removed tenant-scoped management URLs.
|
||
- Stale internal CTAs/links that previously pointed at `/admin/t/{tenant}` management screens.
|
||
- Tenant slug/identifier mismatch (requesting a tenant not belonging to the active workspace context).
|
||
- Cross-scope leakage via global search results or navigation items.
|
||
- Non-member users attempting to infer tenant/workspace existence.
|
||
|
||
## Requirements *(mandatory)*
|
||
|
||
**Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior,
|
||
or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates
|
||
(preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests.
|
||
If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries.
|
||
|
||
**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST:
|
||
- state which authorization plane(s) are involved (tenant `/admin/t/{tenant}` vs platform `/system`),
|
||
- ensure any cross-plane access is deny-as-not-found (404),
|
||
- explicitly define 404 vs 403 semantics:
|
||
- non-member / not entitled to tenant scope → 404 (deny-as-not-found)
|
||
- member but missing capability → 403
|
||
- describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change,
|
||
- reference the canonical capability registry (no raw capability strings; no role-string checks in feature code),
|
||
- ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics),
|
||
- ensure destructive-like actions require confirmation (`->requiresConfirmation()`),
|
||
- include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated.
|
||
|
||
**Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange)
|
||
on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages.
|
||
|
||
**Constitution alignment (BADGE-001):** If this feature changes status-like badges (status/outcome/severity/risk/availability/boolean),
|
||
the spec MUST describe how badge semantics stay centralized (no ad-hoc mappings) and which tests cover any new/changed values.
|
||
|
||
### Functional Requirements
|
||
|
||
**Principles (normative):**
|
||
- “Tenants” means the workspace-managed tenants list (CRUD under `/admin/tenants*`).
|
||
- “Switch tenant” is a context action in tenant scope; it must not behave like a CRUD list.
|
||
- Management must not depend on being in tenant scope; tenant scope must not expose tenant administration CRUD.
|
||
|
||
**Routing / IA (normative):**
|
||
- Workspace management is canonical under `/admin/*`:
|
||
- `/admin/tenants` (list/create)
|
||
- `/admin/tenants/{tenant}` (manage overview)
|
||
- `/admin/tenants/{tenant}/memberships`
|
||
- `/admin/tenants/{tenant}/provider-connections`
|
||
- `/admin/tenants/{tenant}/required-permissions`
|
||
- `/admin/tenants/{tenant}/onboarding` (optional entry)
|
||
- `/admin/operations` and `/admin/operations/{run}` (monitoring)
|
||
- Tenant scope is operate-only under `/admin/t/{tenant}/*` (inventory, drift, policies, backups/restore, directory, etc.).
|
||
|
||
**Workspace context:**
|
||
- Workspace-scoped management routes MUST use the `/admin/tenants*` shape and rely on the selected workspace context (e.g., middleware) rather than including `{workspace}` in every management URL.
|
||
|
||
**Route parameter identity (normative):**
|
||
- `{tenant}` in both workspace-managed routes (`/admin/tenants/{tenant}/*`) and tenant-scoped routes (`/admin/t/{tenant}/*`) MUST refer to the managed tenant identifier `Tenant.external_id` (Entra tenant GUID).
|
||
|
||
**Authorization semantics (mandatory):**
|
||
- Non-member / not entitled to scope: 404 (deny-as-not-found).
|
||
- Member lacking capability: 403 for management mutations.
|
||
- Workspace management pages are viewable for workspace members even without manage capabilities; only mutations are capability-gated.
|
||
- All checks are server-side via Policies/Gates, using the canonical capability registry (no raw strings).
|
||
|
||
**Panel structure (native):**
|
||
- Two Filament panels are used:
|
||
- Workspace panel is tenantless and mounted at `/admin`.
|
||
- Tenant panel is tenancy-mounted at `/admin/t/{tenant}`.
|
||
- Each resource/page is registered only in the appropriate panel.
|
||
|
||
**Global search isolation (mandatory):**
|
||
- Global search behavior is defined normatively in **FR-080-014**; this section captures intent only (no cross-scope discovery leaks).
|
||
|
||
**Route removal (dev-stage requirement):**
|
||
- Tenant-scoped management routes must not exist after migration (expected 404):
|
||
- `/admin/t/{tenant}/provider-connections*`
|
||
- `/admin/t/{tenant}/required-permissions*`
|
||
- `/admin/t/{tenant}/memberships*`
|
||
- `/admin/t/{tenant}/tenants*` (any management tenancy list/view/edit)
|
||
|
||
**FR-080-001**: The system MUST expose tenant administration surfaces only under workspace scope (`/admin/tenants*`).
|
||
**FR-080-002**: The system MUST expose tenant operations only under tenant scope (`/admin/t/{tenant}/*`).
|
||
**FR-080-003**: All workspace management pages MUST require active workspace context + workspace membership; non-member returns 404.
|
||
**FR-080-004**: All tenant operational routes MUST require workspace membership + tenant entitlement; non-entitled returns 404.
|
||
**FR-080-005**: Management mutations MUST return 403 when capability is missing (in addition to any UI disabling).
|
||
|
||
**Management surfaces (workspace-scoped):**
|
||
**FR-080-006**: Tenants list and tenant manage overview MUST exist under `/admin/tenants` and `/admin/tenants/{tenant}`.
|
||
**FR-080-007**: Provider Connections CRUD MUST exist only under `/admin/tenants/{tenant}/provider-connections*`.
|
||
**FR-080-008**: Required Permissions remediation UI MUST exist only under `/admin/tenants/{tenant}/required-permissions` and render DB-only.
|
||
**FR-080-009**: Tenant Memberships/Roles management MUST exist only under `/admin/tenants/{tenant}/memberships` and audit changes.
|
||
**FR-080-010**: Activation/onboarding controls MUST live under workspace-managed tenant pages/wizard entry (not in tenant scope).
|
||
|
||
**Navigation (enterprise):**
|
||
**FR-080-011**: Workspace navigation MUST include Tenants (manage) and Monitoring (Operations, Alerts, Audit Log).
|
||
**FR-080-012**: Tenant navigation MUST include only operational modules; no tenant CRUD entry appears in tenant sidebar.
|
||
**FR-080-014**: Workspace panel global search MAY return managed tenants only when the user can access them; tenant panel global search MUST NOT include tenant-management resources.
|
||
**FR-080-015**: Workspace-managed tenant management routes MUST be reachable under `/admin/tenants*` with a selected workspace context; `/admin/w/{workspace}` is not the canonical management route shape for this feature.
|
||
|
||
**Filament constraint (hard rule):** any globally searchable Resource MUST have an Edit or View page; otherwise global search will return no results.
|
||
|
||
**Observability & Audit (minimal, mandatory):**
|
||
**FR-080-013**: The system MUST emit audit events for management mutations (tenant changes, role changes, provider connection CRUD/default selection, activation/onboarding state changes) with redacted fields only.
|
||
|
||
### Key Entities *(include if feature involves data)*
|
||
|
||
- **Workspace**: Portfolio/customer context; primary security boundary for `/admin/*`.
|
||
- **ManagedTenant**: Workspace-owned representation of an Entra/Intune tenant (identified by Entra tenant GUID).
|
||
- **TenantMembership**: Assignment of a user to a managed tenant with a role (Owner/Manager/Operator/Readonly).
|
||
- **ProviderConnection**: Stored connection/config for provider integration, scoped to a managed tenant.
|
||
- **Capability**: Canonical capability registry entries used for authorization decisions.
|
||
- **AuditLog entry**: Append-only event record for management mutations (redacted).
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-080-001**: Tenant management surfaces are reachable only under `/admin/tenants*`.
|
||
- **SC-080-002**: Tenant operational surfaces are reachable only under `/admin/t/{tenant}/*`.
|
||
- **SC-080-003**: Tenant-scoped management routes are not registered and return 404.
|
||
- **SC-080-004**: Authorization semantics are consistent: non-member 404, missing capability yields 403 on mutations.
|
||
- **SC-080-005**: Internal CTAs/links to manage provider connections/required permissions point to workspace-managed routes.
|